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
343
example/main.c
343
example/main.c
|
|
@ -8,7 +8,8 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* Walks through the libooc life cycle twice over, once per subclass.
|
||||
* Walks through the libooc life cycle four times, once per class in the
|
||||
* hierarchy.
|
||||
*
|
||||
* The Dog walk covers the whole API: allocation, a second reference through a
|
||||
* base class, reading and writing fields by name, the private field rules, and
|
||||
|
|
@ -16,10 +17,18 @@
|
|||
* second subclass, so the output makes the one thing that is not visible in the
|
||||
* source clear: `animal_speak` is a single call site and still reaches Dog's
|
||||
* implementation for one object and Cat's for the other.
|
||||
*
|
||||
* Garfield and Snoopy go one level deeper, each on its own branch, and they are
|
||||
* what turn a list of classes into a hierarchy worth looking at. The last walk
|
||||
* puts all four objects through one Animal * and asks what each really is, which
|
||||
* is the question a caller with no static type information can only answer by
|
||||
* asking.
|
||||
*/
|
||||
|
||||
#include "cat.h"
|
||||
#include "dog.h"
|
||||
#include "garfield.h"
|
||||
#include "snoopy.h"
|
||||
|
||||
#include <ooc/ooc.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -27,15 +36,32 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Copy `text` onto the heap, because a field that owns its value needs heap. */
|
||||
/*
|
||||
* Copy `text` onto the heap, because a field that owns its value needs heap.
|
||||
*
|
||||
* The example classes have their own private copies of this; it is duplicated
|
||||
* here rather than shared because main.c is a caller, not part of the hierarchy,
|
||||
* and has no business reaching into a class' internals for it.
|
||||
*
|
||||
* Returns NULL if `text` is NULL or the allocation fails.
|
||||
*/
|
||||
static char *copy_of(const char *text)
|
||||
{
|
||||
size_t len = strlen(text);
|
||||
char *copy = malloc(len + 1);
|
||||
++len;
|
||||
size_t len;
|
||||
char *copy;
|
||||
|
||||
if (!text)
|
||||
return NULL;
|
||||
|
||||
len = strlen(text);
|
||||
|
||||
if (len == SIZE_MAX)
|
||||
return NULL;
|
||||
|
||||
copy = malloc(len + 1);
|
||||
|
||||
if (copy)
|
||||
memcpy(copy, text, len);
|
||||
memcpy(copy, text, len + 1);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
|
@ -260,6 +286,302 @@ fail:
|
|||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Garfield walk: a subclass of a subclass.
|
||||
*
|
||||
* Nothing here is new in kind. The object is created and released the same way
|
||||
* as a Dog, fields resolve by name the same way, and the ownership and underscore
|
||||
* rules are the library's rather than the class's. The differences are all
|
||||
* consequences of the extra level, and they are what the walk is for.
|
||||
*
|
||||
* Lookup now crosses two class records instead of one. "favourite_food" and
|
||||
* "_meals" are Garfield's own, "colour" and "_lives" are found at Cat_class, and
|
||||
* "name" and "age" are found at Animal_class -- so a name declared two levels up
|
||||
* is as reachable as one declared immediately above.
|
||||
*
|
||||
* The private rules also cross the level unchanged, and that is worth seeing:
|
||||
* `_meals` belongs to Garfield, `_lives` to Cat, and a caller can read both by
|
||||
* name but write neither. A subclass sitting between the caller and the
|
||||
* declaration does not weaken the rule.
|
||||
*
|
||||
* Returns 0 when the walk completes and -1 if a step the example relies on was
|
||||
* refused.
|
||||
*/
|
||||
static int visit_garfield(void)
|
||||
{
|
||||
Garfield *garfield;
|
||||
Cat *cat_view;
|
||||
Animal *animal;
|
||||
char *food;
|
||||
int servings = 7;
|
||||
|
||||
garfield = garfield_new("Garfield", 4, "orange", "lasagna");
|
||||
if (!garfield) {
|
||||
fprintf(stderr, "failed to create garfield\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Upcasting is unchecked in C, so these two casts are just arithmetic that
|
||||
* happens to be zero: every class in the chain embeds the one above it as
|
||||
* its first member. A handle to the middle of the chain works exactly like a
|
||||
* handle to the top or the bottom, which is why the same lookup and the same
|
||||
* vtable work through any of them.
|
||||
*/
|
||||
cat_view = &garfield->cat;
|
||||
animal = &garfield->cat.animal;
|
||||
|
||||
printf("through a Cat handle, the same object answers as %s.\n",
|
||||
*(char **)ooc_get(cat_view, "name"));
|
||||
|
||||
printf("%s is a %d year old %s %s who has eaten %d meal%s.\n",
|
||||
*(char **)ooc_get(garfield, "name"),
|
||||
*(int *)ooc_get(garfield, "age"),
|
||||
*(char **)ooc_get(garfield, "colour"),
|
||||
*(char **)ooc_get(garfield, "favourite_food"),
|
||||
garfield_meals(garfield),
|
||||
garfield_meals(garfield) == 1 ? "" : "s");
|
||||
|
||||
/*
|
||||
* The owned field is the class's own, so replacing it hands the old string
|
||||
* over and frees it -- the same rule as a breed or a colour, one level down.
|
||||
*/
|
||||
food = copy_of("pizza");
|
||||
if (!food || ooc_set(garfield, "favourite_food", &food) != 0) {
|
||||
fprintf(stderr, "failed to set favourite food\n");
|
||||
free(food);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
printf("%s now prefers %s.\n", *(char **)ooc_get(garfield, "name"),
|
||||
*(char **)ooc_get(garfield, "favourite_food"));
|
||||
|
||||
/* Both private fields are readable and neither is writable. */
|
||||
if (ooc_set(garfield, "_meals", &servings) != 0)
|
||||
printf("_meals is private, and reads back as %d\n",
|
||||
*(int *)ooc_get(garfield, "_meals"));
|
||||
if (ooc_set(garfield, "_lives", &servings) != 0)
|
||||
printf("_lives is Cat's, and reads back as %d\n",
|
||||
*(int *)ooc_get(garfield, "_lives"));
|
||||
printf("__legs is readable from three levels down: %s\n",
|
||||
ooc_get(garfield, "__legs") ? "yes" : "no");
|
||||
|
||||
/*
|
||||
* The exact type is the deepest class record, reached without a walk, and
|
||||
* the subtype test walks all three links to say the same thing the long way.
|
||||
*/
|
||||
printf("a garfield is exactly a Garfield: %s, a Cat: %s, an Animal: %s\n",
|
||||
garfield->cat.animal.object.class == &Garfield_class ? "yes" : "no",
|
||||
ooc_is_a(garfield, &Cat_class) ? "yes" : "no",
|
||||
ooc_is_a(garfield, &Animal_class) ? "yes" : "no");
|
||||
|
||||
/* The other branch of the hierarchy is not reachable from here. */
|
||||
printf("a garfield is a Snoopy: %s, and has an imagination: %s\n",
|
||||
ooc_is_a(garfield, &Snoopy_class) ? "yes" : "no",
|
||||
ooc_get(garfield, "imagination") ? "yes" : "no");
|
||||
|
||||
/* One call site, and the vtable decides which speak() runs. */
|
||||
animal_speak(animal);
|
||||
|
||||
/*
|
||||
* One reference only, despite the three handles to it: cat_view and animal
|
||||
* alias the same storage, so releasing any one of them twice would destroy
|
||||
* the object early. A pointer to a base class is a view, not a new claim.
|
||||
*/
|
||||
ooc_release(garfield);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ooc_release(garfield);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Snoopy walk: the other branch, and a hidden field one level down.
|
||||
*
|
||||
* The same calls as visit_garfield(), against a Dog subclass rather than a Cat
|
||||
* one, so the differences are the ones the fork causes. `__flights` carries two
|
||||
* underscores, which ooc_get() withholds as well as ooc_set() refusing to
|
||||
* write it, so snoopy_flights() is the only way in -- where Garfield's `_meals`
|
||||
* is still readable by name.
|
||||
*
|
||||
* Returns 0 when the walk completes and -1 if a step the example relies on was
|
||||
* refused.
|
||||
*/
|
||||
static int visit_snoopy(void)
|
||||
{
|
||||
Snoopy *snoopy;
|
||||
Animal *animal;
|
||||
char *dream;
|
||||
int hours = 12;
|
||||
|
||||
snoopy = snoopy_new("Snoopy", 3, "beagle", "flying his red baron");
|
||||
if (!snoopy) {
|
||||
fprintf(stderr, "failed to create snoopy\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
animal = &snoopy->dog.animal;
|
||||
|
||||
printf("%s is a %d year old %s dreaming of %s, with %d flights.\n",
|
||||
*(char **)ooc_get(snoopy, "name"),
|
||||
*(int *)ooc_get(snoopy, "age"),
|
||||
*(char **)ooc_get(snoopy, "breed"),
|
||||
*(char **)ooc_get(snoopy, "imagination"),
|
||||
snoopy_flights(snoopy));
|
||||
|
||||
/* The owned field again, this time one declared by a Dog subclass. */
|
||||
dream = copy_of("a chicken dinner");
|
||||
if (!dream || ooc_set(snoopy, "imagination", &dream) != 0) {
|
||||
fprintf(stderr, "failed to set imagination\n");
|
||||
free(dream);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
printf("%s is now dreaming of %s.\n", *(char **)ooc_get(snoopy, "name"),
|
||||
*(char **)ooc_get(snoopy, "imagination"));
|
||||
|
||||
/*
|
||||
* Two underscores, so there is no way in by name in either direction. The
|
||||
* class reports the value itself instead, which is the whole point of hiding
|
||||
* it: the caller is told what it may know and nothing more.
|
||||
*/
|
||||
printf("__flights is readable by name: %s, writable: %s\n",
|
||||
ooc_get(snoopy, "__flights") ? "yes" : "no",
|
||||
ooc_set(snoopy, "__flights", &hours) == 0 ? "yes" : "no");
|
||||
printf("and the class still reports %d flights.\n",
|
||||
snoopy_flights(snoopy));
|
||||
|
||||
printf("a snoopy is a Dog: %s, a Cat: %s, a Garfield: %s\n",
|
||||
ooc_is_a(snoopy, &Dog_class) ? "yes" : "no",
|
||||
ooc_is_a(snoopy, &Cat_class) ? "yes" : "no",
|
||||
ooc_is_a(snoopy, &Garfield_class) ? "yes" : "no");
|
||||
|
||||
animal_speak(animal);
|
||||
|
||||
ooc_release(snoopy);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ooc_release(snoopy);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The hierarchy walk: four objects, one pointer type, no static information.
|
||||
*
|
||||
* This is what the runtime is for. Each object is created as its own class and
|
||||
* then handled only as an Animal *, with the pointer's type carrying nothing at
|
||||
* all about what it points to. The single animal_speak() call below reaches four
|
||||
* different implementations, and the ooc_is_a() answers come from the objects
|
||||
* themselves rather than from the call site.
|
||||
*
|
||||
* The order is the hierarchy: a root, then a class on each branch, then the two
|
||||
* subclasses of a subclass. Reading the output top to bottom shows what a
|
||||
* three-level chain does to a call that knows none of it.
|
||||
*
|
||||
* Returns 0 when the walk completes.
|
||||
*/
|
||||
static int visit_hierarchy(void)
|
||||
{
|
||||
Animal *rex;
|
||||
Animal *mia;
|
||||
Animal *garfield;
|
||||
Animal *snoopy;
|
||||
|
||||
printf("\n-- one pointer type, four runtime types --\n");
|
||||
|
||||
/* Each object is created as its own class and only used as an Animal. */
|
||||
rex = (Animal *)dog_new("Rex", 5, "German Shepherd");
|
||||
mia = (Animal *)cat_new("Mia", 3, "tabby");
|
||||
garfield = (Animal *)garfield_new("Garfield", 4, "orange", "lasagna");
|
||||
snoopy = (Animal *)snoopy_new("Snoopy", 3, "beagle", "his red baron");
|
||||
|
||||
if (!rex || !mia || !garfield || !snoopy) {
|
||||
fprintf(stderr, "failed to create the hierarchy\n");
|
||||
|
||||
/* Release whatever did get built, since each reference is its own. */
|
||||
ooc_release(rex);
|
||||
ooc_release(mia);
|
||||
ooc_release(garfield);
|
||||
ooc_release(snoopy);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Four calls to one function. Which speak() runs is decided by the vtable
|
||||
* each object carries, and the pointer type plays no part in it -- the whole
|
||||
* of the polymorphism is visible right here in the output.
|
||||
*/
|
||||
animal_speak(rex);
|
||||
animal_speak(mia);
|
||||
animal_speak(garfield);
|
||||
animal_speak(snoopy);
|
||||
|
||||
/*
|
||||
* And the runtime type can be recovered from the object alone. This is the
|
||||
* question a caller with an Animal * and no static information has to ask,
|
||||
* and the chain walk answers it for a base class as readily as for the exact
|
||||
* type -- which is the direction that is actually useful.
|
||||
*/
|
||||
printf("\n%s is a Dog: %d, a Cat: %d, an Animal: %d\n",
|
||||
*(char **)ooc_get(rex, "name"),
|
||||
ooc_is_a(rex, &Dog_class),
|
||||
ooc_is_a(rex, &Cat_class),
|
||||
ooc_is_a(rex, &Animal_class));
|
||||
|
||||
printf("%s is a Dog: %d, a Cat: %d, a Garfield: %d\n",
|
||||
*(char **)ooc_get(garfield, "name"),
|
||||
ooc_is_a(garfield, &Dog_class),
|
||||
ooc_is_a(garfield, &Cat_class),
|
||||
ooc_is_a(garfield, &Garfield_class));
|
||||
|
||||
printf("%s is a Snoopy: %d, a Dog: %d, a Cat: %d\n",
|
||||
*(char **)ooc_get(snoopy, "name"),
|
||||
ooc_is_a(snoopy, &Snoopy_class),
|
||||
ooc_is_a(snoopy, &Dog_class),
|
||||
ooc_is_a(snoopy, &Cat_class));
|
||||
|
||||
/*
|
||||
* A field declared on one branch is not reachable from the other. Both
|
||||
* names are absent from both chains, which is why both calls return NULL and
|
||||
* why asking for the wrong branch's field is safe rather than corrupting
|
||||
* memory at a bogus offset.
|
||||
*/
|
||||
printf("\n%s has an imagination: %s, and a colour: %s\n",
|
||||
*(char **)ooc_get(snoopy, "name"),
|
||||
ooc_get(snoopy, "imagination") ? "yes" : "no",
|
||||
ooc_get(snoopy, "colour") ? "yes" : "no");
|
||||
|
||||
printf("%s has a favourite food: %s, and a breed: %s\n",
|
||||
*(char **)ooc_get(garfield, "name"),
|
||||
ooc_get(garfield, "favourite_food") ? "yes" : "no",
|
||||
ooc_get(garfield, "breed") ? "yes" : "no");
|
||||
|
||||
/* One reference each, so four releases. */
|
||||
ooc_release(rex);
|
||||
ooc_release(mia);
|
||||
ooc_release(garfield);
|
||||
ooc_release(snoopy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Run the walks in order, stopping at the first failure.
|
||||
*
|
||||
* Each walk takes one class through the whole life cycle and returns 0 on
|
||||
* success or -1 if a step the example relies on was refused, which is enough to
|
||||
* make a broken expectation visible without aborting. The short-circuit keeps the
|
||||
* output of a failing run readable: no later walk can be trusted once an earlier
|
||||
* one has misbehaved.
|
||||
*
|
||||
* The exit status is nonzero if any walk failed, so this doubles as the test the
|
||||
* `test` target runs. Returns 0 when everything succeeded.
|
||||
*/
|
||||
int main(void)
|
||||
{
|
||||
int status = visit_dog();
|
||||
|
|
@ -267,5 +589,14 @@ int main(void)
|
|||
if (status == 0)
|
||||
status = visit_cat();
|
||||
|
||||
if (status == 0)
|
||||
status = visit_garfield();
|
||||
|
||||
if (status == 0)
|
||||
status = visit_snoopy();
|
||||
|
||||
if (status == 0)
|
||||
status = visit_hierarchy();
|
||||
|
||||
return status != 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue