Added garfield and snoopy classes to ./example which are derived from the cat and dog classes.
This commit is contained in:
parent
6ca258f882
commit
32e7d0b337
13 changed files with 1519 additions and 69 deletions
96
README.md
96
README.md
|
|
@ -45,17 +45,69 @@ If installed under `/usr/local` and your pkg-config does not search there:
|
|||
|
||||
## Example
|
||||
|
||||
The `example/` directory contains an `Animal` base class and two derived classes, `Dog` and `Cat`. Both override the virtual `speak` method, and `example/main.c` walks each of them through the same calls, so the output shows one call site reaching two different implementations. Objects are reference-counted with `ooc_retain()` and `ooc_release()`.
|
||||
The `example/` directory holds a small class hierarchy. `Animal` is the root,
|
||||
`Dog` and `Cat` derive from it, and `Garfield` and `Snoopy` derive from those:
|
||||
|
||||
Animal
|
||||
|-- Dog
|
||||
| `-- Snoopy
|
||||
`-- Cat
|
||||
`-- Garfield
|
||||
|
||||
Every class overrides the virtual `speak` method, and `example/main.c` walks each
|
||||
one through the same calls before putting all four objects through a single
|
||||
`Animal *`. The output makes the point that is invisible in the source: one call
|
||||
site reaches four implementations, and the vtable decides which.
|
||||
|
||||
make test # runs example/main.c
|
||||
make safety-test # runs the assertion suite in tests/
|
||||
|
||||
Objects are reference-counted with `ooc_retain()` and `ooc_release()`.
|
||||
|
||||
The public installed header is:
|
||||
|
||||
#include <ooc/ooc.h>
|
||||
|
||||
Application-specific classes such as `Animal`, `Dog` and `Cat` are not installed by the library.
|
||||
|
||||
### Constructors down a chain
|
||||
|
||||
Each class publishes an `init` function alongside its constructor, so a subclass
|
||||
builds the classes above it by calling them rather than repeating what they do:
|
||||
|
||||
```c
|
||||
int garfield_init(Garfield *garfield, const char *name, int age,
|
||||
const char *colour, const char *favourite_food);
|
||||
```
|
||||
|
||||
`garfield_init()` calls `cat_init()`, which calls `animal_init()`. Each one
|
||||
installs its own vtable, and the one above overwrites it, so the object ends up
|
||||
speaking as the most derived class. A subclass of `Garfield` would call
|
||||
`garfield_init()` in turn, and the composition extends without anything else
|
||||
changing.
|
||||
|
||||
### Destructors down a chain
|
||||
|
||||
The library calls the destructor belonging to the object's runtime type and stops
|
||||
there. A derived destructor is therefore responsible for the whole hierarchy, not
|
||||
only for its own members, and the frees grow with the depth:
|
||||
|
||||
```c
|
||||
static void destroy(ooc_object *object)
|
||||
{
|
||||
Garfield *garfield = (Garfield *)object;
|
||||
|
||||
free(garfield->favourite_food); /* Garfield's */
|
||||
free(garfield->cat.colour); /* Cat's, allocated by cat_init() */
|
||||
free(garfield->cat.animal.name); /* Animal's, allocated by animal_init() */
|
||||
}
|
||||
```
|
||||
|
||||
Cat's own destructor never runs for a Garfield, and Animal's never runs for
|
||||
either. Omitting a middle free is silent -- the object works perfectly well and
|
||||
only a memory checker reports the leak, which is why `make safety-test` is worth
|
||||
running under Valgrind or AddressSanitizer.
|
||||
|
||||
## Fields by name
|
||||
|
||||
A class publishes the fields that may be reached dynamically as a NULL-terminated
|
||||
|
|
@ -91,6 +143,17 @@ chain: a `Dog` resolves `name` and `age` through `Animal_class`, and a field
|
|||
declared by a subclass shadows a same-named field of its base. Upcasting a
|
||||
pointer does not change this -- the runtime type of the object decides.
|
||||
|
||||
Lookup walks as far as the chain goes, and a class inserted in the middle hides
|
||||
nothing. A `Garfield` resolves its own `favourite_food`, Cat's `colour` and
|
||||
Animal's `name`, while `imagination` -- declared on the other branch -- resolves
|
||||
to nothing at all:
|
||||
|
||||
```c
|
||||
ooc_get(garfield, "colour"); /* Cat's */
|
||||
ooc_get(garfield, "name"); /* Animal's, two records up */
|
||||
ooc_get(garfield, "imagination"); /* NULL -- Snoopy's field, not in this chain */
|
||||
```
|
||||
|
||||
`ooc_set()` copies the field's storage with overlap-safe semantics and returns `-1` for an
|
||||
unknown field name or a NULL argument, so a bad name is reported instead of
|
||||
writing out of bounds.
|
||||
|
|
@ -114,6 +177,11 @@ not be shared with a second field, or the same allocation would be freed twice.
|
|||
Assigning a field the pointer it already holds frees nothing. The destructor
|
||||
still frees whatever the field holds when the object is released.
|
||||
|
||||
The rule is the library's rather than the class', so it applies to an inherited
|
||||
field exactly as to one declared alongside it. Setting a Garfield's inherited
|
||||
`colour` releases the string Cat's constructor allocated, the same as setting its
|
||||
own `favourite_food` does.
|
||||
|
||||
## Type checks
|
||||
|
||||
`ooc_is_a()` walks the inheritance chain from the object's runtime type, so it
|
||||
|
|
@ -125,6 +193,15 @@ ooc_is_a(cat, &Animal_class); /* 1 -- a Cat is an Animal */
|
|||
ooc_is_a(cat, &Dog_class); /* 0 -- siblings are unrelated */
|
||||
```
|
||||
|
||||
The walk covers the whole chain, so depth costs nothing to the caller:
|
||||
|
||||
```c
|
||||
ooc_is_a(garfield, &Garfield_class); /* 1 */
|
||||
ooc_is_a(garfield, &Cat_class); /* 1 -- Garfield -> Cat -> Animal */
|
||||
ooc_is_a(garfield, &Animal_class); /* 1 */
|
||||
ooc_is_a(garfield, &Snoopy_class); /* 0 -- the other branch */
|
||||
```
|
||||
|
||||
This is the useful direction to ask in, because an upcast in C is unchecked: a
|
||||
caller holding an `Animal *` uses it to find out what the object really is.
|
||||
|
||||
|
|
@ -193,6 +270,20 @@ subclass cannot reach a base class' private field either. A class writes its
|
|||
own fields through the struct definition, where the members are in view and no
|
||||
accessor is needed.
|
||||
|
||||
The rules hold at any depth. `Garfield` has a `_meals` of its own while
|
||||
inheriting Cat's `_lives`, and a caller can read both by name but write neither --
|
||||
the rule belongs to the declaration, not to the object:
|
||||
|
||||
```c
|
||||
ooc_get(garfield, "_meals"); /* readable -- Garfield's */
|
||||
ooc_get(garfield, "_lives"); /* readable -- Cat's, two levels up */
|
||||
ooc_set(garfield, "_meals", &n); /* refused */
|
||||
ooc_set(garfield, "_lives", &n); /* also refused */
|
||||
```
|
||||
|
||||
Private means private to the declaring class, which is why a subclass adds a
|
||||
field of its own rather than leaning on the base class' private state.
|
||||
|
||||
Two things this is not. It does not keep a determined caller from reading a
|
||||
single-underscore field, since `ooc_get()` hands back the field's real storage
|
||||
and C has no access control -- the name is a contract, not a wall. And it does
|
||||
|
|
@ -204,7 +295,8 @@ pointer.
|
|||
|
||||
include/ooc/ooc.h Public API
|
||||
src/ooc.c Library implementation
|
||||
example/ Complete example
|
||||
example/ Complete example: Animal, Dog, Cat, Garfield, Snoopy
|
||||
tests/safety.c Assertion suite
|
||||
Makefile Build/install rules
|
||||
libooc.pc.in pkg-config template
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue