Initial commit.
This commit is contained in:
commit
f59561fdfb
15 changed files with 2516 additions and 0 deletions
274
example/animal.c
Normal file
274
example/animal.c
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* This file is part of libooc.
|
||||
* https://xw3.org/hanez/libooc
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Animal -- the base class of the example.
|
||||
*
|
||||
* It owns a copy of its name, hands ownership of every instance to the
|
||||
* caller, and forwards `speak` to the vtable so that subclasses can override
|
||||
* the behaviour.
|
||||
*/
|
||||
|
||||
#include "animal.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Copy `text` onto the heap; the caller owns the result.
|
||||
*
|
||||
* A class that holds a string allocates it once here and hands the copy to the
|
||||
* field, which is why the destructor has something to free. The copy is
|
||||
* independent of `text`, so a name built from a stack buffer or a literal
|
||||
* outlives the call.
|
||||
*
|
||||
* Returns NULL if `text` is NULL or the allocation fails, which the callers
|
||||
* here treat as a failed construction. The terminator is copied along, so the
|
||||
* result is a C string and nothing has to be added to it.
|
||||
*/
|
||||
static char *dupstr(const char *text)
|
||||
{
|
||||
size_t len;
|
||||
char *copy;
|
||||
|
||||
if (!text)
|
||||
return NULL;
|
||||
|
||||
len = strlen(text);
|
||||
if (len == SIZE_MAX)
|
||||
return NULL;
|
||||
++len;
|
||||
|
||||
copy = malloc(len);
|
||||
if (copy)
|
||||
memcpy(copy, text, len);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
* Virtual destructor: releases what the constructor allocated.
|
||||
*
|
||||
* ooc_release() calls this with the object's storage still intact and frees the
|
||||
* object itself afterwards, so the members are readable here and only the
|
||||
* members need releasing. `name` is the one allocation Animal owns, and the
|
||||
* "name" field is marked as owned, so ooc_set() frees an old string when it is
|
||||
* replaced and the value that is left is freed here.
|
||||
*
|
||||
* The cast is free because the ooc_object header is the first member of Animal,
|
||||
* which is what makes the address a valid Animal * in the first place. A
|
||||
* subclass overrides this through its own `destroy`, and then it is also
|
||||
* responsible for the members it added.
|
||||
*/
|
||||
static void destroy(ooc_object *object)
|
||||
{
|
||||
Animal *animal = (Animal *)object;
|
||||
|
||||
free(animal->name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Default implementation of AnimalVTable::speak.
|
||||
*
|
||||
* Animal is the base class, so this is what a subclass that does not care to
|
||||
* override gets: it prints the name, which is why the name has to be a real
|
||||
* string rather than a pointer into somewhere else.
|
||||
*
|
||||
* The parameter is an Animal * rather than the runtime type, so an override
|
||||
* casts it back to its own class -- see speak() in dog.c.
|
||||
*/
|
||||
static void speak(Animal *animal)
|
||||
{
|
||||
printf("%s makes a sound.\n", animal->name ? animal->name : "(unnamed)");
|
||||
}
|
||||
|
||||
/*
|
||||
* Animal's own vtable.
|
||||
*
|
||||
* One table per class, shared by every instance, which is why the field in the
|
||||
* struct is a pointer to a const table rather than the table itself. A subclass
|
||||
* installs its own, so an instance of the derived class reaches the derived
|
||||
* `speak` through the same call.
|
||||
*/
|
||||
static const AnimalVTable vt = {
|
||||
.speak = speak,
|
||||
};
|
||||
|
||||
/*
|
||||
* The fields ooc_get() and ooc_set() may reach by name.
|
||||
*
|
||||
* offsetof() and sizeof() keep the table in step with the struct, so a changed
|
||||
* member type or position needs no edit here. The last column marks a field
|
||||
* the class owns: `name` is a string on the heap, so ooc_set() releases the
|
||||
* old one when a new name is assigned, while `age` is copied as it stands.
|
||||
*
|
||||
* The two underscore names are published so the library can enforce what they
|
||||
* mean. "_id" is readable and refuses to be written, and "__legs" is readable
|
||||
* by neither, which is why the class hands it out through animal_legs().
|
||||
*/
|
||||
static const ooc_field Animal_fields[] = {
|
||||
{ "name", offsetof(Animal, name), sizeof(((Animal *)0)->name), 1 },
|
||||
{ "age", offsetof(Animal, age), sizeof(((Animal *)0)->age), 0 },
|
||||
{ "_id", offsetof(Animal, _id), sizeof(((Animal *)0)->_id), 0 },
|
||||
{ "__legs", offsetof(Animal, __legs), sizeof(((Animal *)0)->__legs), 0 },
|
||||
{ NULL, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/*
|
||||
* Animal's runtime type record.
|
||||
*
|
||||
* `size` is sizeof(Animal) and not the size of a base, because ooc_new()
|
||||
* allocates exactly this much and the derived classes that embed Animal need
|
||||
* room for their own members on top. `destroy` is the destructor above,
|
||||
* `super` is NULL because Animal is a root class, and `fields` is the table
|
||||
* that gives ooc_get() and ooc_set() something to resolve.
|
||||
*
|
||||
* The record is a file-scope constant, which is what ooc_new() writes into
|
||||
* every object it allocates.
|
||||
*/
|
||||
const ooc_class Animal_class = {
|
||||
.size = sizeof(Animal),
|
||||
.destroy = destroy,
|
||||
.super = NULL,
|
||||
.fields = Animal_fields,
|
||||
};
|
||||
|
||||
/*
|
||||
* Hand out the next serial number.
|
||||
*
|
||||
* File-scope so the numbers keep climbing across calls, and private to this
|
||||
* file so no other class can hand them out or reset them. It is the only thing
|
||||
* that decides what `_id` becomes.
|
||||
*/
|
||||
static int next_id = 1;
|
||||
|
||||
/*
|
||||
* Initialise the members Animal owns.
|
||||
*
|
||||
* Separate from animal_new() so a subclass constructor can build the base part
|
||||
* instead of duplicating it: dog_new() calls this and then installs its own
|
||||
* vtable and its own members. The `vtable` it sets is Animal's, which a
|
||||
* subclass is expected to overwrite.
|
||||
*
|
||||
* The caller supplies storage that is already an object, ooc_new() having
|
||||
* allocated it, so there is nothing here to release: the name is copied into a
|
||||
* field the object now owns and the destructor frees it. The two underscore
|
||||
* fields are written here and nowhere else, which is what keeps them to the
|
||||
* class. The field table publishes both so the library knows where they are,
|
||||
* and the leading underscores tell ooc_set() and ooc_get() to leave them alone.
|
||||
*
|
||||
* Requires zero-initialised members and externally synchronized constructors.
|
||||
* Returns -1 for NULL, repeated initialisation, exhausted IDs or a failed name
|
||||
* copy. Failure leaves the object unchanged and still owned by the caller.
|
||||
*/
|
||||
int animal_init(Animal *animal, const char *name, int age)
|
||||
{
|
||||
char *copy;
|
||||
|
||||
if (!animal || animal->vtable || animal->name || next_id == 0)
|
||||
return -1;
|
||||
|
||||
copy = dupstr(name);
|
||||
|
||||
if (!copy)
|
||||
return -1;
|
||||
|
||||
animal->vtable = &vt;
|
||||
animal->name = copy;
|
||||
animal->age = age;
|
||||
animal->_id = next_id;
|
||||
next_id = next_id == INT_MAX ? 0 : next_id + 1;
|
||||
animal->__legs = 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read back `_id`, the field a caller may also reach with ooc_get().
|
||||
*
|
||||
* An accessor alongside the by-name route rather than instead of it, and the
|
||||
* difference is the const: this one does not require a writable object, so it
|
||||
* can be called on a const Animal *.
|
||||
*
|
||||
* Returns 0 for a NULL object, which is indistinguishable from a real id of
|
||||
* zero, so a caller that has to tell the two apart should check the pointer
|
||||
* first. Ids start at 1.
|
||||
*/
|
||||
int animal_id(const Animal *animal)
|
||||
{
|
||||
return animal ? animal->_id : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read back `__legs`, the field nothing outside the class can reach by name.
|
||||
*
|
||||
* ooc_get(dog, "__legs") returns NULL, so this accessor is the only way to
|
||||
* learn the value -- which is the whole point of a two-underscore name. The
|
||||
* class decides what a caller is told, and could as well return something
|
||||
* derived from `__legs` instead of the field itself.
|
||||
*
|
||||
* Returns 0 for a NULL object, as above.
|
||||
*/
|
||||
int animal_legs(const Animal *animal)
|
||||
{
|
||||
return animal ? animal->__legs : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create an Animal named `name` and return it, or NULL.
|
||||
*
|
||||
* The object is allocated through ooc_new() and comes back with a reference
|
||||
* count of one, so the caller owns it and must pass it to ooc_release()
|
||||
* eventually. animal_init() fills in the members, and a failure there releases
|
||||
* the half-built object, since the destructor is already in place and finds
|
||||
* `name` still NULL to free.
|
||||
*
|
||||
* This is the constructor a caller normally uses; animal_init() is what a
|
||||
* subclass uses instead, because it is building the base part of a larger
|
||||
* object rather than a whole one.
|
||||
*
|
||||
* Returns NULL if the allocation fails or the name could not be copied. In
|
||||
* both cases nothing is left to release.
|
||||
*/
|
||||
Animal *animal_new(const char *name, int age)
|
||||
{
|
||||
Animal *animal = ooc_new(&Animal_class);
|
||||
|
||||
if (!animal)
|
||||
return NULL;
|
||||
|
||||
if (animal_init(animal, name, age) != 0) {
|
||||
ooc_release(animal);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return animal;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatch `speak` through the vtable.
|
||||
*
|
||||
* The call is virtual: which implementation runs is decided by the vtable the
|
||||
* object carries, not by the static type of the pointer handed in. A Dog and an
|
||||
* Animal pointer to the same object therefore print different things.
|
||||
*
|
||||
* The three checks are what keep the call safe on a partial object. A NULL
|
||||
* animal is skipped, a NULL vtable means the object was never initialised, and
|
||||
* a NULL `speak` means a vtable that does not implement the method. All three
|
||||
* are tolerated silently, since a class is not obliged to implement anything.
|
||||
*/
|
||||
void animal_speak(Animal *animal)
|
||||
{
|
||||
if (animal && animal->vtable && animal->vtable->speak)
|
||||
animal->vtable->speak(animal);
|
||||
}
|
||||
107
example/animal.h
Normal file
107
example/animal.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* This file is part of libooc.
|
||||
* https://xw3.org/hanez/libooc
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*/
|
||||
|
||||
#ifndef ANIMAL_H
|
||||
#define ANIMAL_H
|
||||
|
||||
#include <ooc/ooc.h>
|
||||
|
||||
typedef struct Animal Animal;
|
||||
typedef struct AnimalVTable AnimalVTable;
|
||||
|
||||
/*
|
||||
* Virtual methods inherited by every animal.
|
||||
*
|
||||
* The vtable is a plain struct of function pointers, so dispatch is an ordinary
|
||||
* call through a pointer and needs no support from the compiler. A subclass
|
||||
* fills in its own table and installs it in the instance, which is how `speak`
|
||||
* reaches Dog's implementation when the object is only known as an Animal.
|
||||
*
|
||||
* Each pointer is called with the object as its first argument, so an
|
||||
* implementation casts it back to the class it belongs to.
|
||||
*/
|
||||
struct AnimalVTable {
|
||||
void (*speak)(Animal *self);
|
||||
};
|
||||
|
||||
/*
|
||||
* Base class.
|
||||
*
|
||||
* Every instance starts with an ooc_object header, which the runtime needs
|
||||
* for reference counting and the virtual destructor. Subclasses embed this
|
||||
* struct as their first member, which makes the upcast free.
|
||||
*/
|
||||
struct Animal {
|
||||
ooc_object object;
|
||||
const AnimalVTable *vtable;
|
||||
char *name;
|
||||
int age;
|
||||
|
||||
/*
|
||||
* Two fields that belong to the class alone, told apart by their leading
|
||||
* underscores.
|
||||
*
|
||||
* `_id` is published, so it can be read by name, and ooc_set() refuses to
|
||||
* write it. `__legs` is hidden outright: ooc_get() withholds it as well, so
|
||||
* nothing outside Animal can read or write it by name at all. Both are set
|
||||
* from the struct definition below, where the members are in view.
|
||||
*/
|
||||
int _id;
|
||||
int __legs;
|
||||
};
|
||||
|
||||
/*
|
||||
* Animal's runtime type record, the value ooc_new() takes.
|
||||
*
|
||||
* A subclass names it in its own `super`, which is how field lookup continues
|
||||
* up the chain and how Dog's objects still resolve "name" and "age".
|
||||
*/
|
||||
extern const ooc_class Animal_class;
|
||||
|
||||
/*
|
||||
* Create an Animal named `name` and return it, or NULL.
|
||||
*
|
||||
* Ownership of the result is the caller's, with a reference count of one, so it
|
||||
* has to reach ooc_release() eventually. Returns NULL if the allocation or the
|
||||
* copy of the name fails, in which case there is nothing to release.
|
||||
*/
|
||||
Animal *animal_new(const char *name, int age);
|
||||
|
||||
/*
|
||||
* Read back `_id`, the field ooc_get() also returns.
|
||||
*
|
||||
* Takes a const object, so it can be called where ooc_get() could not.
|
||||
*/
|
||||
int animal_id(const Animal *animal);
|
||||
|
||||
/*
|
||||
* Read back `__legs`, the field ooc_get() withholds.
|
||||
*
|
||||
* This accessor is the only way to learn the value, which is the point of a
|
||||
* two-underscore name. Returns 0 for a NULL object.
|
||||
*/
|
||||
int animal_legs(const Animal *animal);
|
||||
|
||||
/*
|
||||
* Initialise the members Animal owns, so a subclass constructor can build the
|
||||
* base part without duplicating it. Requires zero-initialised member storage
|
||||
* and external synchronization between constructors in different threads.
|
||||
* Returns 0, or -1 for NULL, an already initialised animal, exhausted positive
|
||||
* int IDs, or a failed name copy. Failure leaves the animal unchanged.
|
||||
*/
|
||||
int animal_init(Animal *animal, const char *name, int age);
|
||||
|
||||
/*
|
||||
* Dispatch `speak` through the vtable, so the implementation depends on the
|
||||
* object's runtime type. A NULL object, a NULL vtable and a NULL method are all
|
||||
* tolerated and do nothing.
|
||||
*/
|
||||
void animal_speak(Animal *animal);
|
||||
|
||||
#endif /* ANIMAL_H */
|
||||
215
example/cat.c
Normal file
215
example/cat.c
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* This file is part of libooc.
|
||||
* https://xw3.org/hanez/libooc
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Cat -- a second Animal subclass, alongside Dog.
|
||||
*
|
||||
* The point of having two is that nothing here is special to Dog: the same three
|
||||
* steps make any subclass, and a Cat is a different runtime type with its own
|
||||
* vtable and its own destructor. A caller holding an Animal * for a Cat and one
|
||||
* for a Dog gets different behaviour out of the same call, which is the whole
|
||||
* argument for the vtable.
|
||||
*
|
||||
* Where Cat differs from Dog is in its members. `colour` is another owned string,
|
||||
* while `_lives` is an int marked private by its leading underscore, so the
|
||||
* class owns it and ooc_set() turns a write away even though the caller can
|
||||
* still read it by name.
|
||||
*/
|
||||
|
||||
#include "cat.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Copy `text` onto the heap; the caller owns the result.
|
||||
*
|
||||
* A private copy of Animal's helper, and identical to it. Each class keeps its
|
||||
* own: exposing dupstr() would mean every subclass reaching into a base class'
|
||||
* internals to build its members, which is the coupling subclassing is meant to
|
||||
* remove. The size is checked before the terminator is added, so a string long
|
||||
* enough to wrap cannot ask malloc() for a short buffer.
|
||||
*
|
||||
* Returns NULL if `text` is NULL or the allocation fails.
|
||||
*/
|
||||
static char *dupstr(const char *text)
|
||||
{
|
||||
size_t len;
|
||||
char *copy;
|
||||
|
||||
if (!text)
|
||||
return NULL;
|
||||
|
||||
len = strlen(text);
|
||||
if (len == SIZE_MAX)
|
||||
return NULL;
|
||||
++len;
|
||||
|
||||
copy = malloc(len);
|
||||
if (copy)
|
||||
memcpy(copy, text, len);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
* Virtual destructor: releases everything cat_new() allocated.
|
||||
*
|
||||
* This replaces Animal's destructor rather than running after it, because the
|
||||
* library calls the one belonging to the object's runtime type and stops there.
|
||||
* So a Cat is responsible for Animal's members too: `name` is freed below even
|
||||
* though animal_init() allocated it, and Animal's own destroy() never runs for
|
||||
* a Cat. The second free is the half of a derived destructor that is easy to
|
||||
* forget.
|
||||
*
|
||||
* `_lives` needs no freeing, being an int, which is worth noticing: what a
|
||||
* destructor releases is the allocations a constructor made, not the members.
|
||||
*
|
||||
* The order of the two frees does not matter, since the allocations are
|
||||
* independent, and both are owned: "colour" is marked as owned in the field
|
||||
* table and "name" in Animal's, so ooc_set() releases a string when it replaces
|
||||
* one and whatever is left is freed here.
|
||||
*/
|
||||
static void destroy(ooc_object *object)
|
||||
{
|
||||
Cat *cat = (Cat *)object;
|
||||
|
||||
free(cat->colour);
|
||||
free(cat->animal.name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cat's implementation of AnimalVTable::speak.
|
||||
*
|
||||
* The signature is Animal's, not Cat's: a vtable entry is called through the
|
||||
* base class' type, so the implementation casts its parameter back down. That
|
||||
* cast is safe only because Animal is the first member of Cat, which makes the
|
||||
* two addresses the same.
|
||||
*
|
||||
* The members are read through the cast, including the ones Animal owns, so
|
||||
* `name` and `_lives` are as available here as they are in Animal's own speak().
|
||||
* The fallbacks keep a cleared string from reaching printf(), since a field set
|
||||
* to NULL is a legitimate state -- ooc_set(dog, "name", NULL) is allowed to
|
||||
* succeed -- and a vtable entry is called with whatever the object currently
|
||||
* holds.
|
||||
*/
|
||||
static void speak(Animal *animal)
|
||||
{
|
||||
Cat *cat = (Cat *)animal;
|
||||
|
||||
printf("%s says: Meow! (%s, %d lives left)\n",
|
||||
cat->animal.name ? cat->animal.name : "(unnamed)",
|
||||
cat->colour ? cat->colour : "(unknown colour)",
|
||||
cat->_lives);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cat's own vtable, identical in shape to Animal's and holding Cat's speak.
|
||||
*
|
||||
* A second table with the same layout as Animal's, which is what a vtable buys:
|
||||
* the slot is chosen by the class that owns the table, not by the type of the
|
||||
* pointer the caller happens to have. Cat and Dog both override `speak`, and
|
||||
* neither can tell the other about it.
|
||||
*/
|
||||
static const AnimalVTable vt = {
|
||||
.speak = speak,
|
||||
};
|
||||
|
||||
/*
|
||||
* The fields Cat adds on top of the ones Animal publishes.
|
||||
*
|
||||
* A derived class lists only what it declares itself; the rest is inherited
|
||||
* rather than repeated. Anything missing here -- "name", "age", "_id" and
|
||||
* "__legs" -- is still reachable through ooc_get() and ooc_set(), because
|
||||
* Cat_class names Animal_class as its base and lookup walks the chain.
|
||||
*
|
||||
* `colour` is marked as owned, exactly as Dog's breed is, so replacing it
|
||||
* through ooc_set() releases the string it held before. `_lives` carries a
|
||||
* leading underscore, so ooc_set() refuses to write it while ooc_get() still
|
||||
* returns it: the underscore rule works the same on a field the subclass
|
||||
* declared as on one the base declared.
|
||||
*/
|
||||
static const ooc_field Cat_fields[] = {
|
||||
{ "colour", offsetof(Cat, colour), sizeof(((Cat *)0)->colour), 1 },
|
||||
{ "_lives", offsetof(Cat, _lives), sizeof(((Cat *)0)->_lives), 0 },
|
||||
{ NULL, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/*
|
||||
* Cat's runtime type record.
|
||||
*
|
||||
* `super` is what makes Cat an Animal: field lookup continues from
|
||||
* Animal_class, so "name", "age", "_id" and "__legs" resolve even though Cat
|
||||
* does not list them. `size` is sizeof(Cat) rather than sizeof(Animal), since
|
||||
* the allocation has to hold the colour and the lives count as well.
|
||||
*
|
||||
* The record is a file-scope constant, as it is for every class, and ooc_new()
|
||||
* writes its address into each object it allocates -- which is how a Cat and a
|
||||
* Dog stay distinguishable while both are passed around as Animal.
|
||||
*/
|
||||
const ooc_class Cat_class = {
|
||||
.size = sizeof(Cat),
|
||||
.destroy = destroy,
|
||||
.super = &Animal_class,
|
||||
.fields = Cat_fields,
|
||||
};
|
||||
|
||||
/*
|
||||
* Create a Cat named `name` of the given `colour` and return it, or NULL.
|
||||
*
|
||||
* Three steps, in the order a subclass constructor needs them. The object is
|
||||
* allocated through ooc_new() with a reference count of one, so the caller owns
|
||||
* it and must release it. animal_init() fills in the base part, including the
|
||||
* `_id` and `__legs` that only Animal may write. Then the vtable is replaced
|
||||
* with Cat's, so the object answers to Cat's speak rather than Animal's.
|
||||
*
|
||||
* The vtable is overwritten after animal_init() rather than before, since that
|
||||
* call installs Animal's table and this one has to win.
|
||||
*
|
||||
* `_lives` is set here and nowhere else. A caller can read it with ooc_get() and
|
||||
* cannot write it with ooc_set(), so this constructor and any future method of
|
||||
* Cat are the only places the value can change -- which is the point of marking
|
||||
* it with an underscore.
|
||||
*
|
||||
* Each step can fail, and both failures release the object, which is safe
|
||||
* because the destructor is already registered and frees whatever is present:
|
||||
* after a failed animal_init() there is no name and no colour to free, and after
|
||||
* a failed colour copy the name has to be freed, which is exactly what the
|
||||
* destructor does. Returns NULL if any step fails, leaving nothing to release.
|
||||
*/
|
||||
Cat *cat_new(const char *name, int age, const char *colour)
|
||||
{
|
||||
Cat *cat = ooc_new(&Cat_class);
|
||||
|
||||
if (!cat)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Let Animal build the part it owns, including the private `_id`, then
|
||||
* take over the vtable with Cat's own.
|
||||
*/
|
||||
if (animal_init(&cat->animal, name, age) != 0) {
|
||||
ooc_release(cat);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cat->animal.vtable = &vt;
|
||||
cat->colour = dupstr(colour);
|
||||
cat->_lives = 9;
|
||||
|
||||
if (!cat->colour) {
|
||||
ooc_release(cat);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return cat;
|
||||
}
|
||||
67
example/cat.h
Normal file
67
example/cat.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* This file is part of libooc.
|
||||
* https://xw3.org/hanez/libooc
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef CAT_H
|
||||
#define CAT_H
|
||||
|
||||
#include "animal.h"
|
||||
|
||||
typedef struct Cat Cat;
|
||||
|
||||
/*
|
||||
* Derived class.
|
||||
*
|
||||
* The embedded Animal comes first, so a Cat pointer can be handed to any
|
||||
* function expecting an Animal, and Cat adds a colour and a private `_lives` of
|
||||
* its own.
|
||||
*
|
||||
* Two things follow from that ordering. The ooc_object header inside Animal
|
||||
* stays at offset zero, where the library looks for it, and the upcast is a
|
||||
* no-op cast because both addresses are the same -- which is what lets speak()
|
||||
* in cat.c cast its Animal * back to a Cat * without arithmetic. The cost is
|
||||
* that a Cat may not add a member of its own before the base.
|
||||
*
|
||||
* The struct holds no destructor of its own; the one Cat registers lives in
|
||||
* Cat_class and releases both the colour and Animal's name.
|
||||
*/
|
||||
struct Cat {
|
||||
Animal animal;
|
||||
char *colour;
|
||||
|
||||
/*
|
||||
* Private to Cat, and readable by name: one leading underscore stops
|
||||
* ooc_set() from writing it but leaves ooc_get() alone. Animal's `_id` works
|
||||
* the same way, so the rule is the one in the library rather than something
|
||||
* the subclass has to repeat.
|
||||
*/
|
||||
int _lives;
|
||||
};
|
||||
|
||||
/*
|
||||
* Cat's runtime type record, the value ooc_new() takes.
|
||||
*
|
||||
* Its `super` is Animal_class, so a Cat inherits Animal's fields, and its
|
||||
* `size` is sizeof(Cat), so the allocation has room for the colour and the
|
||||
* lives count.
|
||||
*/
|
||||
extern const ooc_class Cat_class;
|
||||
|
||||
/*
|
||||
* Create a Cat named `name` of the given `colour` and return it, or NULL.
|
||||
*
|
||||
* The result has a reference count of one, so ownership is the caller's and it
|
||||
* must reach ooc_release() eventually. Returns NULL if any step of the
|
||||
* construction fails, leaving nothing to release.
|
||||
*
|
||||
* The object is a Cat and answers to Cat's `speak`, whether it is later reached
|
||||
* through a Cat * or an Animal *.
|
||||
*/
|
||||
Cat *cat_new(const char *name, int age, const char *colour);
|
||||
|
||||
#endif /* CAT_H */
|
||||
192
example/dog.c
Normal file
192
example/dog.c
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* This file is part of libooc.
|
||||
* https://xw3.org/hanez/libooc
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dog -- an Animal subclass that overrides the virtual `speak` method.
|
||||
*
|
||||
* Because Dog embeds an Animal as its first member, a Dog can be passed
|
||||
* wherever an Animal is expected and still reach Dog's own vtable.
|
||||
*
|
||||
* Subclassing here means three things, and this file shows each one. The object
|
||||
* is allocated from Dog_class, whose `super` points at Animal_class so the
|
||||
* inherited fields resolve. Animal's members are built by animal_init(), the
|
||||
* base class' own entry point, rather than duplicated. And a separate vtable
|
||||
* and destructor are registered for Dog, so the override and the extra
|
||||
* allocation are cleaned up by the runtime type of the object.
|
||||
*/
|
||||
|
||||
#include "dog.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Copy `text` onto the heap; the caller owns the result.
|
||||
*
|
||||
* A private copy of Animal's helper, and identical to it. It is duplicated
|
||||
* rather than shared because a class keeps its own constructor state: exposing
|
||||
* dupstr() would mean the derived class reaching into a base class' internals
|
||||
* to build its members, which is the coupling subclassing is meant to remove.
|
||||
*
|
||||
* Returns NULL if `text` is NULL or the allocation fails.
|
||||
*/
|
||||
static char *dupstr(const char *text)
|
||||
{
|
||||
size_t len;
|
||||
char *copy;
|
||||
|
||||
if (!text)
|
||||
return NULL;
|
||||
|
||||
len = strlen(text);
|
||||
if (len == SIZE_MAX)
|
||||
return NULL;
|
||||
++len;
|
||||
|
||||
copy = malloc(len);
|
||||
if (copy)
|
||||
memcpy(copy, text, len);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
* Virtual destructor: releases everything dog_new() allocated.
|
||||
*
|
||||
* This replaces Animal's destructor rather than running after it, because the
|
||||
* library calls the one belonging to the object's runtime type and stops
|
||||
* there. So a Dog is responsible for Animal's members too: `name` is freed
|
||||
* below even though animal_init() allocated it, and Animal's own destroy()
|
||||
* never runs for a Dog. The second free is the half of a derived destructor
|
||||
* that is easy to forget.
|
||||
*
|
||||
* The order does not matter, since the two allocations are independent, and
|
||||
* both are owned: "breed" is marked as owned in the field table, and "name" is
|
||||
* marked as owned in Animal's, so ooc_set() releases a string when it replaces
|
||||
* one and what is left here is freed on destruction.
|
||||
*/
|
||||
static void destroy(ooc_object *object)
|
||||
{
|
||||
Dog *dog = (Dog *)object;
|
||||
|
||||
free(dog->breed);
|
||||
free(dog->animal.name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dog's implementation of AnimalVTable::speak.
|
||||
*
|
||||
* The signature is Animal's, not Dog's: a vtable entry is called through the
|
||||
* base class' type, so the implementation casts its parameter back down. That
|
||||
* cast is the cost of a subclass adding behaviour to an existing method, and
|
||||
* it is safe only because Animal is the first member of Dog, which makes the
|
||||
* two addresses the same.
|
||||
*
|
||||
* Adding a *new* virtual method is not possible this way, since the vtable is
|
||||
* the base class' struct: that takes an interface struct of its own, holding
|
||||
* the vtable pointer this class already has as `vtable`.
|
||||
*/
|
||||
static void speak(Animal *animal)
|
||||
{
|
||||
Dog *dog = (Dog *)animal;
|
||||
|
||||
printf("%s says: Woof! (%s)\n", dog->animal.name ? dog->animal.name : "(unnamed)",
|
||||
dog->breed ? dog->breed : "(unknown breed)");
|
||||
}
|
||||
|
||||
/*
|
||||
* Dog's own vtable, identical in shape to Animal's and holding Dog's speak.
|
||||
*
|
||||
* A subclass that overrides nothing reuses the base table as it is, so the
|
||||
* override is what makes a separate table necessary here.
|
||||
*/
|
||||
static const AnimalVTable vt = {
|
||||
.speak = speak,
|
||||
};
|
||||
|
||||
/*
|
||||
* The fields Dog adds on top of the ones Animal publishes.
|
||||
*
|
||||
* A derived class lists only what it declares itself; the rest is inherited
|
||||
* rather than repeated. Anything missing here -- "name", "age", and Animal's
|
||||
* two underscore names -- is still reachable through ooc_get() and ooc_set(),
|
||||
* because Dog_class names Animal_class as its base and lookup walks the chain.
|
||||
*
|
||||
* Like Animal's name, `breed` is marked as owned, so replacing it through
|
||||
* ooc_set() releases the string it held before. The underscores of the
|
||||
* inherited fields keep their meaning across the boundary: ooc_set(dog, "_id")
|
||||
* is still refused even though Dog did not declare it.
|
||||
*/
|
||||
static const ooc_field Dog_fields[] = {
|
||||
{ "breed", offsetof(Dog, breed), sizeof(((Dog *)0)->breed), 1 },
|
||||
{ NULL, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/*
|
||||
* Dog's runtime type record.
|
||||
*
|
||||
* `super` is what makes Dog a Dog: field lookup continues from Animal_class, so
|
||||
* "name", "age", "_id" and "__legs" resolve even though Dog does not list them.
|
||||
* `size` is sizeof(Dog) rather than sizeof(Animal), since the allocation has to
|
||||
* hold the breed as well as the base.
|
||||
*/
|
||||
const ooc_class Dog_class = {
|
||||
.size = sizeof(Dog),
|
||||
.destroy = destroy,
|
||||
.super = &Animal_class,
|
||||
.fields = Dog_fields,
|
||||
};
|
||||
|
||||
/*
|
||||
* Create a Dog named `name` of the given `breed` and return it, or NULL.
|
||||
*
|
||||
* Three steps, in the order a subclass constructor needs them. The object is
|
||||
* allocated through ooc_new() with a reference count of one, so the caller owns
|
||||
* it and must release it. animal_init() fills in the base part, including the
|
||||
* `_id` and `__legs` that only Animal may write. Then the vtable is replaced
|
||||
* with Dog's, so the object answers to Dog's speak rather than Animal's.
|
||||
*
|
||||
* The vtable is overwritten after animal_init() rather than before, since that
|
||||
* call installs Animal's table and this one has to win.
|
||||
*
|
||||
* Each step can fail, and both failures release the object, which is safe
|
||||
* because the destructor is already registered and frees whatever is present:
|
||||
* after a failed animal_init() there is no name and no breed to free, and after
|
||||
* a failed breed copy the name has to be freed, which is exactly what the
|
||||
* destructor does. Returns NULL if any step fails, leaving nothing to release.
|
||||
*/
|
||||
Dog *dog_new(const char *name, int age, const char *breed)
|
||||
{
|
||||
Dog *dog = ooc_new(&Dog_class);
|
||||
|
||||
if (!dog)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Let Animal build the part it owns, including the private `_id`, then
|
||||
* take over the vtable with Dog's own.
|
||||
*/
|
||||
if (animal_init(&dog->animal, name, age) != 0) {
|
||||
ooc_release(dog);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dog->animal.vtable = &vt;
|
||||
dog->breed = dupstr(breed);
|
||||
|
||||
if (!dog->breed) {
|
||||
ooc_release(dog);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dog;
|
||||
}
|
||||
57
example/dog.h
Normal file
57
example/dog.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* This file is part of libooc.
|
||||
* https://xw3.org/hanez/libooc
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*/
|
||||
|
||||
#ifndef DOG_H
|
||||
#define DOG_H
|
||||
|
||||
#include "animal.h"
|
||||
|
||||
typedef struct Dog Dog;
|
||||
|
||||
/*
|
||||
* Derived class.
|
||||
*
|
||||
* The embedded Animal comes first, so a Dog pointer can be handed to any
|
||||
* function expecting an Animal, and Dog adds a breed of its own.
|
||||
*
|
||||
* Two things follow from that ordering. The ooc_object header inside Animal
|
||||
* stays at offset zero, where the library looks for it, and the upcast is a
|
||||
* no-op cast because both addresses are the same -- which is what lets speak()
|
||||
* in dog.c cast its Animal * back to a Dog * without arithmetic. The cost is
|
||||
* that a Dog may not add a member of its own before the base.
|
||||
*
|
||||
* The struct holds no destructor of its own; the one Dog registers lives in
|
||||
* Dog_class and releases both the breed and Animal's name.
|
||||
*/
|
||||
struct Dog {
|
||||
Animal animal;
|
||||
char *breed;
|
||||
};
|
||||
|
||||
/*
|
||||
* Dog's runtime type record, the value ooc_new() takes.
|
||||
*
|
||||
* Its `super` is Animal_class, so a Dog inherits Animal's fields, and its
|
||||
* `size` is sizeof(Dog), so the allocation has room for the breed.
|
||||
*/
|
||||
extern const ooc_class Dog_class;
|
||||
|
||||
/*
|
||||
* Create a Dog named `name` of the given `breed` and return it, or NULL.
|
||||
*
|
||||
* The result has a reference count of one, so ownership is the caller's and it
|
||||
* must reach ooc_release() eventually. Returns NULL if any step of the
|
||||
* construction fails, leaving nothing to release.
|
||||
*
|
||||
* The object is a Dog and answers to Dog's `speak`, whether it is later reached
|
||||
* through a Dog * or an Animal *.
|
||||
*/
|
||||
Dog *dog_new(const char *name, int age, const char *breed);
|
||||
|
||||
#endif /* DOG_H */
|
||||
271
example/main.c
Normal file
271
example/main.c
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
/*
|
||||
* This file is part of libooc.
|
||||
* https://xw3.org/hanez/libooc
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Walks through the libooc life cycle twice over, once per subclass.
|
||||
*
|
||||
* 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
|
||||
* the virtual destructor. The Cat walk then repeats the same calls against a
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "cat.h"
|
||||
#include "dog.h"
|
||||
|
||||
#include <ooc/ooc.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Copy `text` onto the heap, because a field that owns its value needs heap. */
|
||||
static char *copy_of(const char *text)
|
||||
{
|
||||
size_t len = strlen(text);
|
||||
char *copy = malloc(len + 1);
|
||||
++len;
|
||||
|
||||
if (copy)
|
||||
memcpy(copy, text, len);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Dog walk.
|
||||
*
|
||||
* One object taken through the whole life cycle: created, shared through a base
|
||||
* class reference, written and read by field name, spoken through, and
|
||||
* released. Every reference the object collects has to be given back, so both
|
||||
* `dog` and `animal` are released on the way out, including on the error paths.
|
||||
*
|
||||
* Returns 0 when the walk completes and -1 if a step the example relies on was
|
||||
* refused.
|
||||
*/
|
||||
static int visit_dog(void)
|
||||
{
|
||||
Dog *dog;
|
||||
Animal *animal;
|
||||
char *name;
|
||||
int birthday = 6;
|
||||
int weight = 45;
|
||||
|
||||
dog = dog_new("Rex", 5, "German Shepherd");
|
||||
if (!dog) {
|
||||
fprintf(stderr, "failed to create dog\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* The count is now two, so both references have to be released. */
|
||||
animal = ooc_retain((Animal *)dog);
|
||||
if (!animal) {
|
||||
ooc_release(dog);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fields are reachable by name alone, with no struct definition in sight:
|
||||
* "breed" is Dog's own field, "name" and "age" are inherited from Animal.
|
||||
*
|
||||
* ooc_get() hands back the address of the field, so a string field is read
|
||||
* in two steps -- the slot first, the string it points to second.
|
||||
*/
|
||||
printf("%s is a %d year old %s.\n",
|
||||
*(char **)ooc_get(dog, "name"),
|
||||
*(int *)ooc_get(dog, "age"),
|
||||
*(char **)ooc_get(dog, "breed"));
|
||||
|
||||
/* ooc_set() copies a value into the named field. */
|
||||
if (ooc_set(dog, "age", &birthday) != 0) {
|
||||
fprintf(stderr, "failed to set age\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
printf("%s just turned %d.\n", *(char **)ooc_get(dog, "name"),
|
||||
*(int *)ooc_get(dog, "age"));
|
||||
|
||||
/*
|
||||
* "name" is a field the class owns, so assigning a new one hands it over:
|
||||
* the replacement is copied in and the string it displaces is released
|
||||
* here, leaving nothing to free by hand.
|
||||
*
|
||||
* ooc_set() takes the address of the value, so a pointer field is assigned
|
||||
* through a pointer to the pointer -- and the new value has to be heap
|
||||
* memory rather than a literal, because the object would otherwise own
|
||||
* something free() cannot release.
|
||||
*/
|
||||
name = copy_of("Bella");
|
||||
if (!name || ooc_set(dog, "name", &name) != 0) {
|
||||
fprintf(stderr, "failed to set name\n");
|
||||
free(name);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
printf("%s was renamed.\n", *(char **)ooc_get(dog, "name"));
|
||||
|
||||
/* An unknown name is refused rather than written out of bounds. */
|
||||
if (ooc_set(dog, "weight", &weight) != 0)
|
||||
printf("no such field: weight\n");
|
||||
|
||||
/*
|
||||
* A name starting with an underscore belongs to the class that declared
|
||||
* it, so the setter turns it away. The field is still readable by name,
|
||||
* which is what lets a class show its own state without handing out a way
|
||||
* to change it -- note that "_id" is inherited from Animal, and the
|
||||
* refusal follows the name to whichever class in the chain declared it.
|
||||
*/
|
||||
if (ooc_set(dog, "_id", &weight) != 0)
|
||||
printf("_id is private, and reads back as %d\n",
|
||||
*(int *)ooc_get(dog, "_id"));
|
||||
|
||||
/*
|
||||
* Two leading underscores go further and are not readable either:
|
||||
* ooc_get() finds the field and then withholds it, so there is no way in
|
||||
* from here at all. An accessor is the only way to the value, and both
|
||||
* "_id" and "__legs" now report the same through the class instead.
|
||||
*/
|
||||
printf("__legs is readable by name: %s\n",
|
||||
ooc_get(dog, "__legs") ? "yes" : "no");
|
||||
printf("__legs is writable by name: %s\n",
|
||||
ooc_set(dog, "__legs", &weight) == 0 ? "yes" : "no");
|
||||
printf("and the class still reports %d legs, id %d.\n",
|
||||
animal_legs((Animal *)dog), animal_id((Animal *)dog));
|
||||
|
||||
animal_speak(animal);
|
||||
|
||||
ooc_release(dog);
|
||||
ooc_release(animal);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ooc_release(animal);
|
||||
ooc_release(dog);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Cat walk.
|
||||
*
|
||||
* The same calls as visit_dog(), against a second subclass, and that is the
|
||||
* point of having it: nothing below is Cat-specific. Field lookup walks the
|
||||
* chain to Animal for "name" and "age" and stops at Cat for "colour", the
|
||||
* owned-field and private-field rules behave identically, and the destructor
|
||||
* releases both strings because Cat registered its own.
|
||||
*
|
||||
* The runtime type is asked about explicitly at the end, in both directions.
|
||||
* ooc_is_a() answers yes for the cat and for the Animal it derives from and no
|
||||
* for its sibling, and the exact type comes from the object's own class
|
||||
* member.
|
||||
*
|
||||
* Returns 0 when the walk completes and -1 if a step the example relies on was
|
||||
* refused.
|
||||
*/
|
||||
static int visit_cat(void)
|
||||
{
|
||||
Cat *cat;
|
||||
Animal *animal;
|
||||
char *colour;
|
||||
int weight = 45;
|
||||
|
||||
cat = cat_new("Mia", 3, "tabby");
|
||||
if (!cat) {
|
||||
fprintf(stderr, "failed to create cat\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* A second reference again, this time on an object of another type. */
|
||||
animal = ooc_retain((Animal *)cat);
|
||||
if (!animal) {
|
||||
ooc_release(cat);
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("%s is a %d year old %s with %d lives.\n",
|
||||
*(char **)ooc_get(cat, "name"),
|
||||
*(int *)ooc_get(cat, "age"),
|
||||
*(char **)ooc_get(cat, "colour"),
|
||||
*(int *)ooc_get(cat, "_lives"));
|
||||
|
||||
/*
|
||||
* "colour" is owned, exactly as Dog's breed is, so a replacement releases
|
||||
* the string that was there. Same call, same rules, a field the subclass
|
||||
* declared for itself.
|
||||
*/
|
||||
colour = copy_of("calico");
|
||||
if (!colour || ooc_set(cat, "colour", &colour) != 0) {
|
||||
fprintf(stderr, "failed to set colour\n");
|
||||
free(colour);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
printf("%s is now a %s.\n", *(char **)ooc_get(cat, "name"),
|
||||
*(char **)ooc_get(cat, "colour"));
|
||||
|
||||
/*
|
||||
* The underscore rules do not care which class in the chain declared the
|
||||
* field. Cat's own `_lives` is readable and not writable, the same as
|
||||
* Animal's `_id` reached through a Dog, and Animal's hidden `__legs` is
|
||||
* still out of reach from here.
|
||||
*/
|
||||
if (ooc_set(cat, "_lives", &weight) != 0)
|
||||
printf("_lives is private, and reads back as %d\n",
|
||||
*(int *)ooc_get(cat, "_lives"));
|
||||
printf("__legs is readable from a cat: %s\n",
|
||||
ooc_get(cat, "__legs") ? "yes" : "no");
|
||||
printf("and the class still reports %d legs.\n", animal_legs(animal));
|
||||
|
||||
/*
|
||||
* The runtime type is what the object carries, not what the cast says.
|
||||
*
|
||||
* ooc_is_a() walks the inheritance chain, so a cat answers yes for Cat and
|
||||
* for the Animal it derives from, and no for its sibling Dog. Asking about
|
||||
* a base class is the useful direction: it is how a caller holding an
|
||||
* Animal * asks "is this one of mine" without knowing the answer in
|
||||
* advance.
|
||||
*/
|
||||
printf("a cat is a Cat: %s, an Animal: %s, a Dog: %s\n",
|
||||
ooc_is_a(cat, &Cat_class) ? "yes" : "no",
|
||||
ooc_is_a(cat, &Animal_class) ? "yes" : "no",
|
||||
ooc_is_a(cat, &Dog_class) ? "yes" : "no");
|
||||
|
||||
/*
|
||||
* Asking "exactly which type" is a different question, and needs no chain
|
||||
* walk: the class record is a public member of the object, so a single
|
||||
* comparison answers it. This is also what a switch over a tag would use.
|
||||
*/
|
||||
printf("and it is exactly an Animal: %s\n",
|
||||
cat->animal.object.class == &Animal_class ? "yes" : "no");
|
||||
|
||||
/* One call site, and the vtable decides which speak() runs. */
|
||||
animal_speak(animal);
|
||||
|
||||
ooc_release(cat);
|
||||
ooc_release(animal);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
ooc_release(animal);
|
||||
ooc_release(cat);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int status = visit_dog();
|
||||
|
||||
if (status == 0)
|
||||
status = visit_cat();
|
||||
|
||||
return status != 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue