0% found this document useful (0 votes)
70 views

Class and Objects in C

This document discusses classes and objects in C programming. It introduces the concepts of inheritance, dynamic linkage, and metaclasses. Metaclasses are classes that describe other classes, with class descriptions as their objects. This resolves issues with maintaining a single struct for dynamic linkage across all classes. The document proposes defining a root Object class and Class metaclass to avoid infinite regression in defining class descriptions.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
70 views

Class and Objects in C

This document discusses classes and objects in C programming. It introduces the concepts of inheritance, dynamic linkage, and metaclasses. Metaclasses are classes that describe other classes, with class descriptions as their objects. This resolves issues with maintaining a single struct for dynamic linkage across all classes. The document proposes defining a root Object class and Class metaclass to avoid infinite regression in defining class descriptions.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 14

1‌ 

‌ C
‌ lass‌ ‌And‌‌Objects‌‌in‌‌C ‌ ‌
1.1‌‌Requirements‌  ‌
Inheritance‌‌lets‌‌us‌‌evolve‌‌general‌‌data‌‌types‌‌into‌‌more‌‌specialized‌‌ones‌‌and‌‌spares‌  ‌
us‌‌recoding‌‌basic‌‌functionality.‌‌Dynamic‌‌Linkage‌‌helps‌‌us‌‌repair‌‌the‌‌shortcomings‌  ‌
that‌‌a‌‌more‌‌general‌‌data‌‌type‌‌might‌‌have.‌‌What‌‌we‌‌still‌‌need‌‌is‌‌a‌‌clean‌‌global‌  ‌
organization‌‌to‌‌simplify‌‌maintaining‌‌a‌‌larger‌‌system‌‌of‌‌classes:‌  ‌
(1)‌‌all‌‌dynamic‌‌links‌‌have‌‌to‌‌point‌‌to‌‌the‌‌correct‌‌methods‌‌—‌‌e.g.,‌‌a‌‌constructor‌  ‌
should‌‌not‌‌be‌‌inserted‌‌in‌‌the‌‌wrong‌‌place‌‌in‌‌a‌‌class‌‌description;‌  ‌
(2)‌‌we‌‌need‌‌a‌‌coherent‌‌way‌‌to‌‌add,‌‌remove,‌‌or‌‌change‌‌the‌‌order‌‌of‌‌dynamically‌  ‌
linked‌‌methods‌‌for‌‌a‌‌superclass‌‌while‌‌guaranteeing‌‌correct‌‌inheritance‌‌to‌‌its‌  ‌
subclasses;‌  ‌
(3)‌‌there‌‌should‌‌be‌‌no‌‌loopholes‌‌such‌‌as‌‌missing‌‌dynamic‌‌links‌‌or‌‌undefined‌  ‌
methods;‌  ‌
(4)‌‌if‌‌we‌‌inherit‌‌a‌‌dynamically‌‌linked‌‌method,‌‌the‌‌implementation‌‌of‌‌the‌‌superclass‌  ‌
from‌‌which‌‌we‌‌inherit‌‌must‌‌remain‌‌absolutely‌‌unchanged,‌‌i.e.,‌‌inheritance‌‌must‌  ‌
be‌‌possible‌‌using‌‌binary‌‌information‌‌only;‌  ‌
(5)‌‌different‌‌sets‌‌of‌‌classes‌‌should‌‌be‌‌able‌‌to‌‌have‌‌different‌‌sets‌‌of‌‌dynamically‌  ‌
linked‌‌methods.‌  ‌
Mostly,‌‌this‌‌list‌‌indicates‌‌that‌‌maintaining‌‌dynamic‌‌linkage‌‌is‌‌difficult‌‌and‌‌errorprone‌‌—‌‌if‌‌we‌‌cannot‌‌ 
substantially‌‌improve‌‌the‌‌situation‌‌we‌‌may‌‌well‌‌have‌‌created‌  ‌
a‌‌white‌‌elephant.‌  ‌
So‌‌far‌‌we‌‌have‌‌worked‌‌with‌‌a‌‌single‌‌list‌‌of‌‌dynamically‌‌linked‌‌methods,‌‌regardless‌‌of‌‌whether‌‌or‌‌not‌‌ 
it‌‌made‌‌sense‌‌for‌‌a‌‌particular‌‌class.‌‌The‌‌list‌‌was‌‌defined‌‌as‌  ‌
struct‌‌Class‌‌and‌‌it‌‌was‌‌included‌‌wherever‌‌dynamic‌‌linkage‌‌needed‌‌to‌‌be‌‌initialized.‌  ‌
Thanks‌‌to‌‌function‌‌prototypes,‌‌ANSI-C‌‌will‌‌check‌‌that‌‌function‌‌names‌‌like‌  ‌
Point_ctor‌‌fit‌‌the‌‌slots‌‌in‌‌the‌‌class‌‌description,‌‌where‌‌they‌‌are‌‌used‌‌as‌‌static‌‌initializers.‌‌(1)‌‌above‌‌is‌‌ 
only‌‌a‌‌problem‌‌if‌‌several‌‌methods‌‌have‌‌type‌‌compatible‌‌interfaces‌‌or‌‌if‌‌we‌‌change‌‌struct‌‌Class‌‌and‌‌do‌‌ 
a‌‌sloppy‌‌recompilation.‌  ‌
Item‌‌(2),‌‌changing‌‌struct‌‌Class,‌‌sounds‌‌like‌‌a‌‌nightmare‌‌—‌‌we‌‌need‌‌to‌‌manually‌‌access‌‌every‌‌class‌‌ 
implementation‌‌to‌‌update‌‌the‌‌static‌‌initialization‌‌of‌‌the‌‌class‌ 
description,‌‌and‌‌we‌‌can‌‌easily‌‌forget‌‌to‌‌add‌‌a‌‌new‌‌method‌‌in‌‌some‌‌class,‌‌thus‌  ‌
causing‌‌problem‌‌(3).‌  ‌
If‌‌maintaining‌‌a‌‌single‌‌struct‌‌Class‌‌sounds‌‌like‌‌a‌‌challenge‌‌already,‌‌(5)‌‌above‌  ‌
suggests‌‌that‌‌we‌‌should‌‌have‌‌different‌‌versions‌‌of‌‌struct‌‌Class‌‌for‌‌different‌‌sets‌‌of‌  ‌
classes!‌‌The‌‌requirement‌‌is‌‌perfectly‌‌reasonable,‌‌however:‌‌every‌‌class‌‌needs‌‌a ‌ ‌
constructor‌‌and‌‌a‌‌destructor;‌‌for‌‌points,‌‌circles,‌‌and‌‌other‌‌graphical‌‌objects‌‌we‌‌add‌  ‌
drawing‌‌facilities;‌‌atoms‌‌and‌‌strings‌‌need‌‌comparisons;‌‌collections‌‌like‌‌sets,‌‌bags,‌  ‌
or‌‌lists‌‌have‌‌methods‌‌to‌‌add,‌‌find,‌‌and‌‌remove‌‌objects;‌‌and‌‌so‌‌on.‌  ‌
 ‌
 ‌
1.2‌‌Metaclasses‌  ‌
It‌‌turns‌‌out‌‌that‌‌requirement‌‌(5)‌‌does‌‌not‌‌compound‌‌our‌‌problems‌‌—‌‌it‌‌actually‌  ‌
points‌‌the‌‌way‌‌to‌‌solving‌‌them.‌‌Just‌‌like‌‌a‌‌circle‌‌adds‌‌information‌‌to‌‌a‌‌point,‌‌so‌‌do‌  ‌
the‌‌class‌‌descriptions‌‌for‌‌points‌‌and‌‌circles‌‌together‌‌add‌‌information‌‌—‌‌a ‌ ‌
polymorphic‌‌draw()‌‌—‌‌to‌‌the‌‌class‌‌description‌‌for‌‌both‌‌of‌‌these‌‌two‌‌classes.‌  ‌
Put‌‌differently:‌‌As‌‌long‌‌as‌‌two‌‌classes‌‌have‌‌the‌‌same‌‌dynamically‌‌linked‌  ‌
methods,‌‌albeit‌‌with‌‌different‌‌implementations,‌‌they‌‌can‌‌use‌‌the‌‌same‌‌struct‌  ‌
2‌  ‌

Class‌‌to‌‌store‌‌the‌‌links‌‌—‌‌this‌‌is‌‌the‌‌case‌‌for‌‌Point‌‌and‌‌Circle.‌‌Once‌‌we‌‌add‌  ‌
another‌‌dynamically‌‌linked‌‌method,‌‌we‌‌need‌‌to‌‌lengthen‌‌struct‌‌Class‌‌to‌‌provide‌  ‌
room‌‌for‌‌the‌‌new‌‌link‌‌—‌‌this‌‌is‌‌how‌‌we‌‌get‌‌from‌‌a‌‌class‌‌with‌‌only‌‌a‌‌constructor‌  ‌
and‌‌a‌‌destructor‌‌to‌‌a‌‌class‌‌like‌‌Point‌‌with‌‌a‌‌.draw‌‌component‌‌thrown‌‌in.‌  ‌
Lengthening‌‌structures‌‌is‌‌what‌‌we‌‌called‌‌inheritance,‌‌i.e.,‌‌we‌‌discover‌‌that‌  ‌
class‌‌descriptions‌‌with‌‌the‌‌same‌‌set‌‌of‌‌methods‌‌form‌‌a‌‌class,‌‌and‌‌that‌‌there‌‌is‌  ‌
inheritance‌‌among‌‌the‌‌classes‌‌of‌‌class‌‌descriptions!‌  ‌
We‌‌call‌‌a‌‌class‌‌of‌‌class‌‌descriptions‌‌a‌‌metaclass.‌‌A‌‌metaclass‌‌behaves‌‌just‌‌like‌  ‌
a‌‌class:‌‌Point‌‌and‌‌Circle,‌‌the‌‌descriptions‌‌for‌‌all‌‌points‌‌and‌‌all‌‌circles,‌‌are‌‌two‌  ‌
objects‌‌in‌‌a‌‌metaclass‌‌PointClass,‌‌because‌‌they‌‌can‌‌both‌‌describe‌‌how‌‌to‌‌draw.‌‌A ‌ ‌
metaclass‌‌has‌‌methods:‌‌we‌‌can‌‌ask‌‌an‌‌object‌‌like‌‌Point‌‌or‌‌Circle‌‌for‌‌the‌‌size‌‌of‌  ‌
the‌‌objects,‌‌points‌‌or‌‌circles,‌‌that‌‌it‌‌describes,‌‌or‌‌we‌‌could‌‌ask‌‌the‌‌object‌‌Circle‌‌if‌  ‌
Point,‌‌indeed,‌‌describes‌‌the‌‌superclass‌‌of‌‌the‌‌circles.‌  ‌
Dynamically‌‌linked‌‌methods‌‌can‌‌do‌‌different‌‌things‌‌for‌‌objects‌‌from‌‌different‌  ‌
classes.‌‌Does‌‌a‌‌metaclass‌‌need‌‌dynamically‌‌linked‌‌methods?‌‌The‌‌destructor‌‌in‌  ‌
PointClass‌‌would‌‌be‌‌called‌‌as‌‌a‌‌consequence‌‌of‌‌delete(Point)‌‌or‌‌delete(Circle),‌  ‌
i.e.,‌‌when‌‌we‌‌try‌‌to‌‌eliminate‌‌the‌‌class‌‌description‌‌for‌‌points‌‌or‌‌circles.‌‌This‌‌destructor‌‌ought‌‌to‌‌return‌‌ 
a‌‌null‌‌pointer‌‌because‌‌it‌‌is‌‌clearly‌‌not‌‌a‌‌good‌‌idea‌‌to‌‌eliminate‌‌a‌‌class‌‌description.‌‌A‌‌metaclass‌‌ 
constructor‌‌is‌‌much‌‌more‌‌useful:‌  ‌
Circle‌‌=‌‌new(PointClass,‌‌/*‌‌ask‌‌the‌‌metaclass‌‌*/‌  ‌
"Circle",‌‌/*‌‌to‌‌make‌‌a‌‌class‌‌description‌‌*/‌  ‌
Point,‌‌/*‌‌with‌‌this‌‌superclass,‌‌*/‌  ‌
sizeof(struct‌‌Circle),‌‌/*‌‌this‌‌size‌‌for‌‌the‌‌objects,‌‌*/‌  ‌
ctor,‌‌Circle_ctor,‌‌/*‌‌this‌‌constructor,‌‌*/‌  ‌
draw,‌‌Circle_draw,‌‌/*‌‌and‌‌this‌‌drawing‌‌method.‌‌*/‌  ‌
0);‌‌/*‌‌end‌‌of‌‌list‌‌*/‌  ‌
This‌‌call‌‌should‌‌produce‌‌a‌‌class‌‌description‌‌for‌‌a‌‌class‌‌whose‌‌objects‌‌can‌‌be‌‌constructed,‌‌destroyed,‌‌ 
and‌‌drawn.‌‌Because‌‌drawing‌‌is‌‌the‌‌new‌‌idea‌‌common‌‌to‌‌all‌  ‌
class‌‌descriptions‌‌in‌‌PointClass,‌‌it‌‌seems‌‌only‌‌reasonable‌‌to‌‌expect‌‌that‌‌the‌  ‌
PointClass‌‌constructor‌‌would‌‌at‌‌least‌‌know‌‌how‌‌to‌‌deposit‌‌a‌‌link‌‌to‌‌a‌‌drawing‌  ‌
method‌‌in‌‌the‌‌new‌‌description.‌  ‌
Even‌‌more‌‌is‌‌possible:‌‌if‌‌we‌‌pass‌‌the‌‌superclass‌‌description‌‌Point‌‌to‌‌the‌  ‌
PointClass‌‌constructor,‌‌it‌‌should‌‌be‌‌able‌‌to‌‌first‌‌copy‌‌all‌‌the‌‌inherited‌‌links‌‌from‌  ‌
Point‌‌to‌‌Circle‌‌and‌‌then‌‌overwrite‌‌those‌‌which‌‌are‌‌redefined‌‌for‌‌Circle.‌‌This,‌‌however,‌‌completely‌‌ 
solves‌‌the‌‌problem‌‌of‌‌binary‌‌inheritance:‌‌when‌‌we‌‌create‌‌Circle‌  ‌
we‌‌only‌‌specify‌‌the‌‌new‌‌methods‌‌specific‌‌to‌‌circles;‌‌methods‌‌for‌‌points‌‌are‌‌implicitly‌‌inherited‌‌ 
because‌‌their‌‌addresses‌‌can‌‌be‌‌copied‌‌by‌‌the‌‌PointClass‌‌constructor.‌  ‌
 ‌
 ‌
1.3‌‌Roots‌‌—‌‌Object‌‌and‌‌Class‌  ‌
Class‌‌descriptions‌‌with‌‌the‌‌same‌‌set‌‌of‌‌methods‌‌are‌‌the‌‌objects‌‌of‌‌a‌‌metaclass.‌‌A ‌ ‌
metaclass‌‌as‌‌such‌‌is‌‌a‌‌class‌‌and,‌‌therefore,‌‌has‌‌a‌‌class‌‌description.‌‌We‌‌must‌  ‌
assume‌‌that‌‌the‌‌class‌‌descriptions‌‌for‌‌metaclasses‌‌once‌‌again‌‌are‌‌objects‌‌of‌‌meta‌  ‌
(metameta?)‌‌classes,‌‌which‌‌in‌‌turn‌‌are‌‌classes‌‌and‌‌...‌  ‌
It‌‌seems‌‌unwise‌‌to‌‌continue‌‌this‌‌train‌‌of‌‌thought.‌‌Instead,‌‌let‌‌us‌‌start‌‌with‌‌the‌  ‌
most‌‌trivial‌‌objects‌‌imaginable.‌‌We‌‌define‌‌a‌‌class‌‌Object‌‌with‌‌the‌‌ability‌‌to‌‌create,‌  ‌
destroy,‌‌compare,‌‌and‌‌display‌‌objects.‌ 
Interface‌‌Object.h:‌  ‌
3‌  ‌

extern‌‌const‌‌void‌‌*‌‌Object;‌‌/*‌‌new(Object);‌‌*/‌  ‌
void‌‌*‌‌new‌‌(const‌‌void‌‌*‌‌class,‌‌...);‌  ‌
void‌‌delete‌‌(void‌‌*‌‌self);‌  ‌
int‌‌differ‌‌(const‌‌void‌‌*‌‌self,‌‌const‌‌void‌‌*‌‌b);‌  ‌
int‌‌puto‌‌(const‌‌void‌‌*‌‌self,‌‌FILE‌‌*‌‌fp);‌  ‌
Representation‌‌Object.r:‌  ‌
struct‌‌Object‌‌{ ‌ ‌
const‌‌struct‌‌Class‌‌*‌‌class;‌‌/*‌‌object’s‌‌description‌‌*/‌  ‌
};‌  ‌
Next‌‌we‌‌define‌‌the‌‌representation‌‌for‌‌the‌‌class‌‌description‌‌for‌‌objects,‌‌i.e.,‌‌the‌  ‌
structure‌‌to‌‌which‌‌the‌‌component‌‌.class‌‌in‌‌struct‌‌Object‌‌for‌‌our‌‌trivial‌‌objects‌  ‌
points.‌‌Both‌‌structures‌‌are‌‌needed‌‌in‌‌the‌‌same‌‌places,‌‌so‌‌we‌‌add‌‌to‌‌Object.h:‌  ‌
extern‌‌const‌‌void‌‌*‌‌Class;‌‌/*‌‌new(Class,‌‌"name",‌‌super,‌‌size‌  ‌
sel,‌‌meth,‌‌...‌‌0);‌‌*/‌  ‌
and‌‌to‌‌Object.r:‌  ‌
struct‌‌Class‌‌{ ‌ ‌
const‌‌struct‌‌Object‌‌_;‌‌/*‌‌class’‌‌description‌‌*/‌  ‌
const‌‌char‌‌*‌‌name;‌‌/*‌‌class’‌‌name‌‌*/‌  ‌
const‌‌struct‌‌Class‌‌*‌‌super;‌‌/*‌‌class’‌‌super‌‌class‌‌*/‌  ‌
size_t‌‌size;‌‌/*‌‌class’‌‌object’s‌‌size‌‌*/‌  ‌
void‌‌*‌‌(*‌‌ctor)‌‌(void‌‌*‌‌self,‌‌va_list‌‌*‌‌app);‌  ‌
void‌‌*‌‌(*‌‌dtor)‌‌(void‌‌*‌‌self);‌  ‌
int‌‌(*‌‌differ)‌‌(const‌‌void‌‌*‌‌self,‌‌const‌‌void‌‌*‌‌b);‌  ‌
int‌‌(*‌‌puto)‌‌(const‌‌void‌‌*‌‌self,‌‌FILE‌‌*‌‌fp);‌  ‌
};‌  ‌
 ‌
struct‌‌Class‌‌is‌‌the‌‌representation‌‌of‌‌each‌‌element‌‌of‌‌the‌‌first‌‌metaclass‌‌Class.‌  ‌
This‌‌metaclass‌‌is‌‌a‌‌class;‌‌therefore,‌‌its‌‌elements‌‌point‌‌to‌‌a‌‌class‌‌description.‌‌Pointing‌‌to‌‌a‌‌class‌‌ 
description‌‌is‌‌exactly‌‌what‌‌an‌‌Object‌‌can‌‌do,‌‌i.e.,‌‌struct‌‌Class‌  ‌
extends‌‌struct‌‌Object,‌‌i.e.,‌‌Class‌‌is‌‌a‌‌subclass‌‌of‌‌Object!‌  ‌
This‌‌does‌‌not‌‌cause‌‌grief:‌‌objects,‌‌i.e.,‌‌instances‌‌of‌‌the‌‌class‌‌Object,‌‌can‌‌be‌  ‌
created,‌‌destroyed,‌‌compared,‌‌and‌‌displayed.‌‌We‌‌have‌‌decided‌‌that‌‌we‌‌want‌‌to‌  ‌
create‌‌class‌‌descriptions,‌‌and‌‌we‌‌can‌‌write‌‌a‌‌destructor‌‌that‌‌silently‌‌prevents‌‌that‌‌a ‌ ‌
class‌‌description‌‌is‌‌destroyed.‌‌It‌‌may‌‌be‌‌quite‌‌useful‌‌to‌‌be‌‌able‌‌to‌‌compare‌‌and‌  ‌
display‌‌class‌‌descriptions.‌‌However,‌‌this‌‌means‌‌that‌‌the‌‌metaclass‌‌Class‌‌has‌‌the‌  ‌
same‌‌set‌‌of‌‌methods,‌‌and‌‌therefore‌‌the‌‌same‌‌type‌‌of‌‌description,‌‌as‌‌the‌‌class‌  ‌
Object,‌‌i.e.,‌‌the‌‌chain‌‌from‌‌objects‌‌to‌‌their‌‌class‌‌description‌‌and‌‌from‌‌there‌‌to‌‌the‌  ‌
description‌‌of‌‌the‌‌class‌‌description‌‌ends‌‌right‌‌there.‌‌Properly‌‌initialized.‌  ‌
 ‌
 ‌
 ‌
1.4‌‌Subclassing‌‌—‌‌Any‌  ‌
Given‌‌the‌‌descriptions‌‌Class‌‌and‌‌Object,‌‌we‌‌can‌‌already‌‌make‌‌new‌‌objects‌‌and‌  ‌
even‌‌a‌‌new‌‌subclass.‌‌As‌‌an‌‌example,‌‌consider‌‌a‌‌subclass‌‌Any‌‌which‌‌claims‌‌that‌  ‌
all‌‌its‌‌objects‌‌are‌‌equal‌‌to‌‌any‌‌other‌‌object,‌‌i.e.,‌‌Any‌‌overwrites‌‌differ()‌‌to‌‌always‌  ‌
return‌‌zero.‌‌Here‌‌is‌‌the‌‌implementation‌‌of‌‌Any,‌‌and‌‌a‌‌quick‌‌test,‌‌all‌‌in‌‌one‌‌file‌  ‌
any.c:‌  ‌
#include‌‌"Object.h"‌  ‌
4‌  ‌

static‌‌int‌‌Any_differ‌‌(const‌‌void‌‌*‌‌_self,‌‌const‌‌void‌‌*‌‌b)‌  ‌
{‌  ‌
return‌‌0;‌‌/*‌‌Any‌‌equals‌‌anything...‌‌*/‌  ‌
}‌  ‌
 ‌
int‌‌main‌‌()‌  ‌
{‌‌void‌‌*‌‌o‌‌=‌‌new(Object);‌  ‌
const‌‌void‌‌*‌‌Any‌‌= ‌ ‌
new(Class,‌‌"Any",‌‌Object,‌‌sizeOf(o),‌  ‌
differ,‌‌Any_differ,‌  ‌
0);‌  ‌
void‌‌*‌‌a‌‌=‌‌new(Any);‌  ‌
puto(Any,‌‌stdout);‌ 
puto(o,‌‌stdout);‌  ‌
puto(a,‌‌stdout);‌  ‌
if‌‌(differ(o,‌‌o)‌‌==‌‌differ(a,‌‌a))‌  ‌
puts("ok");‌  ‌
if‌‌(differ(o,‌‌a)‌‌!=‌‌differ(a,‌‌o))‌  ‌
puts("not‌‌commutative");‌  ‌
delete(o),‌‌delete(a);‌  ‌
delete(Any);‌  ‌
return‌‌0;‌  ‌
}‌  ‌
If‌‌we‌‌implement‌‌a‌‌new‌‌class‌‌we‌‌need‌‌to‌‌include‌‌the‌‌interface‌‌of‌‌its‌‌superclass.‌  ‌
Any‌‌has‌‌the‌‌same‌‌representation‌‌as‌‌Object‌‌and‌‌the‌‌class‌‌is‌‌so‌‌simple‌‌that‌‌we‌‌do‌  ‌
not‌‌even‌‌need‌‌to‌‌include‌‌the‌‌superclass‌‌representation‌‌file.‌‌The‌‌class‌‌description‌  ‌
Any‌‌is‌‌created‌‌by‌‌requesting‌‌a‌‌new‌‌instance‌‌from‌‌its‌‌metaclass‌‌Class‌‌and‌‌constructing‌‌it‌‌with‌‌the‌‌new‌‌ 
class‌‌name,‌‌the‌‌superclass‌‌description,‌‌and‌‌the‌‌size‌‌of‌‌an‌  ‌
object‌‌of‌‌the‌‌new‌‌class:‌  ‌
const‌‌void‌‌*‌‌Any‌‌= ‌ ‌
new(Class,‌‌"Any",‌‌Object,‌‌sizeOf(o),‌  ‌
differ,‌‌Any_differ,‌  ‌
0);‌  ‌
Additionally,‌‌we‌‌specify‌‌exactly‌‌those‌‌dynamically‌‌linked‌‌methods,‌‌which‌‌we‌  ‌
overwrite‌‌for‌‌the‌‌new‌‌class.‌‌The‌‌method‌‌names‌‌can‌‌appear‌‌in‌‌any‌‌order,‌‌each‌‌is‌  ‌
preceded‌‌by‌‌its‌‌selector‌‌name.‌‌A‌‌zero‌‌terminates‌‌the‌‌list.‌  ‌
The‌‌program‌‌generates‌‌one‌‌instance‌‌o‌‌of‌‌Object‌‌and‌‌one‌‌instance‌‌a‌‌of‌‌Any,‌  ‌
and‌‌displays‌‌the‌‌new‌‌class‌‌description‌‌and‌‌the‌‌two‌‌instances.‌‌Either‌‌instance‌‌cannot‌‌differ‌‌from‌‌itself,‌‌ 
so‌‌the‌‌program‌‌prints‌‌ok.‌‌The‌‌method‌‌differ()‌‌has‌‌been‌  ‌
overwritten‌‌for‌‌Any;‌‌therefore,‌‌we‌‌get‌‌different‌‌results‌‌if‌‌we‌‌compare‌‌o‌‌to‌‌a,‌‌and‌  ‌
vice‌‌versa:‌  ‌
$‌‌any‌  ‌
Class‌‌at‌‌0x101fc‌  ‌
Object‌‌at‌‌0x101f4‌  ‌
Any‌‌at‌‌0x10220‌  ‌
ok‌  ‌
not‌‌commutative‌  ‌
Any:‌‌cannot‌‌destroy‌‌class‌  ‌
5‌  ‌

 ‌
 ‌
Clearly,‌‌we‌‌should‌‌not‌‌be‌‌able‌‌to‌‌delete‌‌a‌‌class‌‌description.‌‌This‌‌error‌‌is‌‌already‌  ‌
detected‌‌during‌‌compilation,‌‌because‌‌delete()‌‌does‌‌not‌‌accept‌‌a‌‌pointer‌‌to‌‌an‌‌area‌  ‌
protected‌‌with‌‌const.‌  ‌
 ‌
 ‌
1.5‌‌Implementation‌‌—‌‌Object‌  ‌
Implementing‌‌the‌‌Object‌‌class‌‌is‌‌straightforward:‌‌the‌‌constructor‌‌and‌‌destructor‌  ‌
return‌‌self,‌‌and‌‌differ()‌‌checks‌‌if‌‌its‌‌two‌‌argument‌‌pointers‌‌are‌‌equal.‌‌Defining‌  ‌
these‌‌trivial‌‌implementations‌‌is‌‌very‌‌important,‌‌however:‌‌we‌‌use‌‌a‌‌single‌‌tree‌‌of‌  ‌
classes‌‌and‌‌make‌‌Object‌‌the‌‌ultimate‌‌superclass‌‌of‌‌every‌‌other‌‌class;‌‌if‌‌a‌‌class‌  ‌
does‌‌not‌‌overwrite‌‌a‌‌method‌‌such‌‌as‌‌differ()‌‌it‌‌inherits‌‌it‌‌from‌‌Object,‌‌i.e.,‌‌every‌  ‌
class‌‌has‌‌at‌‌least‌‌a‌‌rudimentary‌‌definition‌‌for‌‌every‌‌dynamically‌‌linked‌‌method‌  ‌
already‌‌applicable‌‌to‌‌Object.‌  ‌
This‌‌is‌‌a‌‌general‌‌safety‌‌principle:‌‌whenever‌‌we‌‌introduce‌‌a‌‌new‌‌dynamically‌  ‌
linked‌‌method,‌‌we‌‌will‌‌immediately‌‌implement‌‌it‌‌for‌‌its‌‌first‌‌class.‌‌In‌‌this‌‌fashion‌  ‌
we‌‌can‌‌never‌‌be‌‌caught‌‌selecting‌‌a‌‌totally‌‌undefined‌‌method.‌‌A‌‌case‌‌in‌‌point‌‌is‌‌the‌  ‌
puto()‌‌method‌‌for‌‌Object:‌  ‌
static‌‌int‌‌Object_puto‌‌(const‌‌void‌‌*‌‌_self,‌‌FILE‌‌*‌‌fp)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌class‌‌=‌‌classOf(_self);‌ 
return‌‌fprintf(fp,‌‌"%s‌‌at‌‌%p\n",‌‌class‌‌—>‌‌name,‌‌_self);‌  ‌
}‌  ‌
Every‌‌object‌‌points‌‌to‌‌a‌‌class‌‌description‌‌and‌‌we‌‌have‌‌stored‌‌the‌‌class‌‌name‌‌with‌  ‌
the‌‌description.‌‌Therefore,‌‌for‌‌any‌‌object‌‌we‌‌can‌‌at‌‌least‌‌display‌‌the‌‌class‌‌name‌  ‌
and‌‌the‌‌address‌‌of‌‌the‌‌object.‌‌The‌‌first‌‌three‌‌lines‌‌of‌‌output‌‌from‌‌the‌‌trivial‌‌test‌  ‌
program‌ ‌indicate‌‌that‌‌we‌‌have‌‌not‌‌bothered‌‌to‌‌overwrite‌‌this‌‌method‌  ‌
for‌‌Class‌‌or‌‌Any.‌  ‌
puto()‌‌relies‌‌on‌‌an‌‌access‌‌function‌‌classOf()‌‌which‌‌does‌‌some‌‌safety‌‌checks‌  ‌
and‌‌returns‌‌the‌‌class‌‌descriptor‌‌for‌‌an‌‌object:‌  ‌
const‌‌void‌‌*‌‌classOf‌‌(const‌‌void‌‌*‌‌_self)‌  ‌
{‌‌const‌‌struct‌‌Object‌‌*‌‌self‌‌=‌‌_self;‌  ‌
assert(self‌‌&&‌‌self‌‌—>‌‌class);‌  ‌
return‌‌self‌‌—>‌‌class;‌  ‌
}‌  ‌
Similarly,‌‌we‌‌can‌‌ask‌‌an‌‌object‌‌for‌‌its‌‌size*‌‌—‌‌remember‌‌that,‌‌technically,‌‌an‌‌object‌  ‌
is‌‌a‌‌plain‌‌void‌‌*‌‌in‌‌ANSI-C:‌  ‌
size_t‌‌sizeOf‌‌(const‌‌void‌‌*‌‌_self)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌class‌‌=‌‌classOf(_self);‌ 
return‌‌class‌‌—>‌‌size;‌  ‌
}‌  ‌
It‌‌is‌‌debatable‌‌if‌‌we‌‌should‌‌ask‌‌the‌‌object‌‌for‌‌the‌‌size,‌‌or‌‌if‌‌we‌‌should‌‌only‌‌ask‌‌it‌‌for‌  ‌
the‌‌class‌‌and‌‌then‌‌explicitly‌‌ask‌‌the‌‌class‌‌for‌‌the‌‌size.‌‌If‌‌we‌‌implement‌‌sizeOf()‌‌for‌  ‌
 ‌
 ‌
*‌‌The‌‌spelling‌‌is‌‌likely‌‌to‌‌be‌‌error-prone,‌‌but‌‌I‌‌just‌‌could‌‌not‌‌resist‌‌the‌‌pun.‌‌Inventing‌‌good‌‌method‌  ‌
names‌‌is‌‌an‌‌art.‌  ‌
 ‌
6‌  ‌

 ‌
objects,‌‌we‌‌cannot‌‌apply‌‌it‌‌to‌‌a‌‌class‌‌description‌‌to‌‌get‌‌the‌‌corresponding‌‌object‌  ‌
size‌‌—‌‌we‌‌will‌‌get‌‌the‌‌size‌‌of‌‌the‌‌class‌‌description‌‌itself.‌‌However,‌‌practical‌‌use‌  ‌
indicates‌‌that‌‌defining‌‌sizeOf()‌‌for‌‌objects‌‌is‌‌preferable.‌‌In‌‌contrast,‌‌super()‌‌is‌‌a ‌ ‌
statically‌‌linked‌‌method‌‌which‌‌returns‌‌the‌‌superclass‌‌of‌‌a‌‌class,‌‌not‌‌of‌‌an‌‌object.‌  ‌
 ‌
 ‌
1.6‌‌Implementation‌‌—‌‌Class‌  ‌
Class‌‌is‌‌a‌‌subclass‌‌of‌‌Object,‌‌so‌‌we‌‌can‌‌simply‌‌inherit‌‌the‌‌methods‌‌for‌‌comparison‌  ‌
and‌‌display.‌‌The‌‌destructor‌‌returns‌‌a‌‌null‌‌pointer‌‌to‌‌keep‌‌delete()‌‌from‌‌actually‌  ‌
reclaiming‌‌the‌‌space‌‌occupied‌‌by‌‌a‌‌class‌‌description:‌  ‌
static‌‌void‌‌*‌‌Class_dtor‌‌(void‌‌*‌‌_self)‌  ‌
{‌‌struct‌‌Class‌‌*‌‌self‌‌=‌‌_self;‌  ‌
fprintf(stderr,‌‌"%s:‌‌cannot‌‌destroy‌‌class\n",‌‌self—>name);‌  ‌
return‌‌0;‌  ‌
}‌  ‌
Here‌‌is‌‌the‌‌access‌‌function‌‌to‌‌get‌‌the‌‌superclass‌‌from‌‌a‌‌class‌‌description:‌  ‌
const‌‌void‌‌*‌‌super‌‌(const‌‌void‌‌*‌‌_self)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌self‌‌=‌‌_self;‌  ‌
assert(self‌‌&&‌‌self‌‌—>‌‌super);‌  ‌
return‌‌self‌‌—>‌‌super;‌  ‌
}‌  ‌
The‌‌only‌‌difficult‌‌part‌‌is‌‌the‌‌implementation‌‌of‌‌the‌‌Class‌‌constructor‌‌because‌  ‌
this‌‌is‌‌where‌‌a‌‌new‌‌class‌‌description‌‌is‌‌initialized,‌‌where‌‌inheritance‌‌takes‌‌place,‌  ‌
and‌‌where‌‌our‌‌four‌‌basic‌‌methods‌‌can‌‌be‌‌overwritten.‌‌We‌‌recall‌‌how‌‌a‌‌new‌‌class‌‌description‌‌is‌‌ 
created:‌  ‌
const‌‌void‌‌*‌‌Any‌‌= ‌ ‌
new(Class,‌‌"Any",‌‌Object,‌‌sizeOf(o),‌  ‌
differ,‌‌Any_differ,‌  ‌
0);‌  ‌
This‌‌means‌‌that‌‌our‌‌Class‌‌constructor‌‌receives‌‌the‌‌name,‌‌superclass,‌‌and‌‌object‌  ‌
size‌‌for‌‌a‌‌new‌‌class‌‌description.‌‌We‌‌start‌‌by‌‌transferring‌‌these‌‌from‌‌the‌‌argument‌  ‌
list:‌  ‌
static‌‌void‌‌*‌‌Class_ctor‌‌(void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌struct‌‌Class‌‌*‌‌self‌‌=‌‌_self;‌  ‌
self‌‌—>‌‌name‌‌=‌‌va_arg(*‌‌app,‌‌char‌‌*);‌  ‌
self‌‌—>‌‌super‌‌=‌‌va_arg(*‌‌app,‌‌struct‌‌Class‌‌*);‌  ‌
self‌‌—>‌‌size‌‌=‌‌va_arg(*‌‌app,‌‌size_t);‌  ‌
assert(self‌‌—>‌‌super);‌  ‌
self‌‌cannot‌‌be‌‌a‌‌null‌‌pointer‌‌because‌‌we‌‌would‌‌not‌‌have‌‌otherwise‌‌found‌‌this‌  ‌
method.‌‌super,‌‌however,‌‌could‌‌be‌‌zero‌‌and‌‌that‌‌would‌‌be‌‌a‌‌very‌‌bad‌‌idea.‌  ‌
The‌‌next‌‌step‌‌is‌‌inheritance.‌‌We‌‌must‌‌copy‌‌the‌‌constructor‌‌and‌‌all‌‌other‌  ‌
methods‌‌from‌‌the‌‌superclass‌‌description‌‌at‌‌super‌‌to‌‌our‌‌new‌‌class‌‌description‌‌at‌ 
self:‌  ‌
const‌‌size_t‌‌offset‌‌=‌‌offsetof(struct‌‌Class,‌‌ctor);‌  ‌
...‌  ‌
memcpy((char‌‌*)‌‌self‌‌+‌‌offset,‌‌(char‌‌*)‌‌self‌‌—>‌‌super‌  ‌
+‌‌offset,‌‌sizeOf(self‌‌—>‌‌super)‌‌—‌‌offset);‌  ‌
7‌  ‌

Assuming‌‌that‌‌the‌‌constructor‌‌is‌‌the‌‌first‌‌method‌‌in‌‌struct‌‌Class,‌‌we‌‌use‌‌the‌‌ANSIC‌‌macro‌‌offsetof()‌‌ 
to‌‌determine‌‌where‌‌our‌‌copy‌‌is‌‌to‌‌start.‌‌Fortunately,‌‌the‌‌class‌  ‌
description‌‌at‌‌super‌‌is‌‌subclassed‌‌from‌‌Object‌‌and‌‌has‌‌inherited‌‌sizeOf()‌‌so‌‌we‌  ‌
can‌‌compute‌‌how‌‌many‌‌bytes‌‌to‌‌copy.‌  ‌
While‌‌this‌‌solution‌‌is‌‌not‌‌entirely‌‌foolproof,‌‌it‌‌seems‌‌to‌‌be‌‌the‌‌best‌‌compromise.‌‌Of‌‌course,‌‌we‌‌could‌‌ 
copy‌‌the‌‌entire‌‌area‌‌at‌‌super‌‌and‌‌store‌‌the‌‌new‌‌name‌  ‌
etc.‌‌afterwards;‌‌however,‌‌we‌‌would‌‌still‌‌have‌‌to‌‌rescue‌‌the‌‌struct‌‌Object‌‌at‌‌the‌  ‌
beginning‌‌of‌‌the‌‌new‌‌class‌‌description,‌‌because‌‌new()‌‌has‌‌already‌‌stored‌‌the‌‌class‌  ‌
description’s‌‌class‌‌description‌‌pointer‌‌there.‌  ‌
The‌‌last‌‌part‌‌of‌‌the‌‌Class‌‌constructor‌‌is‌‌responsible‌‌for‌‌overwriting‌‌whatever‌  ‌
methods‌‌have‌‌been‌‌specified‌‌in‌‌the‌‌argument‌‌list‌‌to‌‌new().‌‌ANSI-C‌‌does‌‌not‌‌let‌‌us‌  ‌
assign‌‌function‌‌pointers‌‌to‌‌and‌‌from‌‌void‌‌*,‌‌so‌‌a‌‌certain‌‌amount‌‌of‌‌casting‌‌is‌  ‌
necessary:‌  ‌
{‌  ‌
typedef‌‌void‌‌(*‌‌voidf)‌‌();‌‌/*‌‌generic‌‌function‌‌pointer‌‌*/‌  ‌
voidf‌‌selector;‌  ‌
va_list‌‌ap‌‌=‌‌*‌‌app;‌  ‌
while‌‌((selector‌‌=‌‌va_arg(ap,‌‌voidf)))‌  ‌
{‌‌voidf‌‌method‌‌=‌‌va_arg(ap,‌‌voidf);‌  ‌
if‌‌(selector‌‌==‌‌(voidf)‌‌ctor)‌  ‌
*‌‌(voidf‌‌*)‌‌&‌‌self‌‌—>‌‌ctor‌‌=‌‌method;‌  ‌
else‌‌if‌‌(selector‌‌==‌‌(voidf)‌‌dtor)‌  ‌
*‌‌(voidf‌‌*)‌‌&‌‌self‌‌—>‌‌dtor‌‌=‌‌method;‌  ‌
else‌‌if‌‌(selector‌‌==‌‌(voidf)‌‌differ)‌  ‌
*‌‌(voidf‌‌*)‌‌&‌‌self‌‌—>‌‌differ‌‌=‌‌method;‌  ‌
else‌‌if‌‌(selector‌‌==‌‌(voidf)‌‌puto)‌  ‌
*‌‌(voidf‌‌*)‌‌&‌‌self‌‌—>‌‌puto‌‌=‌‌method;‌  ‌
}‌  ‌
return‌‌self;‌  ‌
}}‌  ‌
As‌‌we‌‌shall‌‌see‌‌,‌‌this‌‌part‌‌of‌‌the‌‌argument‌‌list‌‌is‌‌best‌‌shared‌‌among‌  ‌
all‌‌class‌‌constructors‌‌so‌‌that‌‌the‌‌selector/method‌‌pairs‌‌may‌‌be‌‌specified‌‌in‌‌any‌  ‌
order.‌‌We‌‌accomplish‌‌this‌‌by‌‌no‌‌longer‌‌incrementing‌‌*‌‌app;‌‌instead‌‌we‌‌pass‌‌a ‌ ‌
copy‌‌ap‌‌of‌‌this‌‌value‌‌to‌‌va_arg().‌  ‌
Storing‌‌the‌‌methods‌‌in‌‌this‌‌fashion‌‌has‌‌a‌‌few‌‌consequences:‌‌If‌‌no‌‌class‌‌constructor‌‌is‌‌interested‌‌in‌‌a ‌‌
selector,‌‌a‌‌selector/method‌‌pair‌‌is‌‌silently‌‌ignored,‌‌but‌‌at‌  ‌
least‌‌it‌‌is‌‌not‌‌added‌‌to‌‌a‌‌class‌‌description‌‌where‌‌it‌‌does‌‌not‌‌belong.‌‌If‌‌a‌‌method‌  ‌
does‌‌not‌‌have‌‌the‌‌proper‌‌type,‌‌the‌‌ANSI-C‌‌compiler‌‌will‌‌not‌‌detect‌‌the‌‌error‌  ‌
because‌‌the‌‌variable‌‌argument‌‌list‌‌and‌‌our‌‌casting‌‌prevent‌‌type‌‌checks.‌‌Here‌‌we‌  ‌
rely‌‌on‌‌the‌‌programmer‌‌to‌‌match‌‌the‌‌selector‌‌to‌‌the‌‌method‌‌supplied‌‌with‌‌it,‌‌but‌  ‌
they‌‌must‌‌be‌‌specified‌‌as‌‌a‌‌pair‌‌and‌‌that‌‌should‌‌result‌‌in‌‌a‌‌certain‌‌amount‌‌of‌‌plausibility.‌  ‌
 ‌
 ‌
1.7‌‌Initialization‌  ‌
Normally‌‌we‌‌obtain‌‌a‌‌class‌‌description‌‌by‌‌sending‌‌new()‌‌to‌‌a‌‌metaclass‌‌description.‌  ‌
In‌‌the‌‌case‌‌of‌‌Class‌‌and‌‌Object‌‌we‌‌would‌‌issue‌‌the‌‌following‌‌calls:‌  ‌
const‌‌void‌‌*‌‌Object‌‌=‌‌new(Class,‌  ‌
"Object",‌‌Object,‌‌sizeof(struct‌‌Object),‌  ‌
8‌  ‌

ctor,‌‌Object_ctor,‌  ‌
dtor,‌‌Object_dtor,‌  ‌
differ,‌‌Object_differ,‌  ‌
puto,‌‌Object_puto,‌  ‌
0);‌  ‌
const‌‌void‌‌*‌‌Class‌‌=‌‌new(Class,‌  ‌
"Class",‌‌Object,‌‌sizeof(struct‌‌Class),‌  ‌
ctor,‌‌Class_ctor,‌  ‌
dtor,‌‌Class_dtor,‌  ‌
0);‌  ‌
Unfortunately,‌‌either‌‌call‌‌relies‌‌on‌‌the‌‌other‌‌already‌‌having‌‌been‌‌completed.‌‌Therefore,‌‌the‌‌ 
implementation‌‌of‌‌Class‌‌and‌‌Object‌‌in‌‌Object.c‌‌requires‌‌static‌‌initialization‌  ‌
of‌‌the‌‌class‌‌descriptions.‌‌This‌‌is‌‌the‌‌only‌‌point‌‌where‌‌we‌‌explicitly‌‌initialize‌‌a‌‌struct‌  ‌
Class:‌  ‌
static‌‌const‌‌struct‌‌Class‌‌object‌‌[]‌‌=‌‌{ ‌ ‌
{‌‌{‌‌object‌‌+‌‌1‌‌},‌  ‌
"Object",‌‌object,‌‌sizeof(struct‌‌Object),‌  ‌
Object_ctor,‌‌Object_dtor,‌‌Object_differ,‌‌Object_puto‌  ‌
},‌  ‌
{‌‌{‌‌object‌‌+‌‌1‌‌},‌  ‌
"Class",‌‌object,‌‌sizeof(struct‌‌Class),‌  ‌
Class_ctor,‌‌Class_dtor,‌‌Object_differ,‌‌Object_puto‌  ‌
}‌  ‌
};‌  ‌
const‌‌void‌‌*‌‌Object‌‌=‌‌object;‌  ‌
const‌‌void‌‌*‌‌Class‌‌=‌‌object‌‌+‌‌1;‌  ‌
An‌‌array‌‌name‌‌is‌‌the‌‌address‌‌of‌‌the‌‌first‌‌array‌‌element‌‌and‌‌can‌‌already‌‌be‌‌used‌‌to‌  ‌
initialize‌‌components‌‌of‌‌the‌‌elements.‌‌We‌‌fully‌‌parenthesize‌‌this‌‌initialization‌‌in‌  ‌
case‌‌struct‌‌Object‌‌is‌‌changed‌‌later‌‌on.‌  ‌
 ‌
 ‌
1.8‌‌Selectors‌  ‌
One‌‌argument‌‌_self‌‌is‌  ‌
the‌‌object‌‌for‌‌dynamic‌‌linkage.‌‌We‌‌verify‌‌that‌‌it‌‌exists‌‌and‌‌that‌‌the‌‌required‌  ‌
method‌‌exists‌‌for‌‌the‌‌object.‌‌Then‌‌we‌‌call‌‌the‌‌method‌‌and‌‌pass‌‌all‌‌arguments‌‌to‌‌it;‌  ‌
therefore,‌‌the‌‌method‌‌can‌‌assume‌‌that‌‌_self‌‌is‌‌a‌‌proper‌‌object‌‌for‌‌it.‌‌Finally,‌‌we‌  ‌
return‌‌the‌‌result‌‌value‌‌of‌‌the‌‌method,‌‌if‌‌any,‌‌as‌‌the‌‌result‌‌of‌‌the‌‌selector.‌  ‌
Every‌‌dynamically‌‌linked‌‌method‌‌must‌‌have‌‌a‌‌selector.‌‌So‌‌far,‌‌we‌‌have‌‌hidden‌  ‌
calls‌‌to‌‌the‌‌constructor‌‌and‌‌the‌‌destructor‌‌behind‌‌new()‌‌and‌‌delete(),‌‌but‌‌we‌‌still‌  ‌
need‌‌the‌‌function‌‌names‌‌ctor‌‌and‌‌dtor‌‌for‌‌the‌‌selector/method‌‌pairs‌‌passed‌‌to‌‌the‌  ‌
Class‌‌constructor.‌‌We‌‌may‌‌later‌‌decide‌‌to‌‌bind‌‌new()‌‌and‌‌delete()‌‌dynamically;‌  ‌
therefore,‌‌it‌‌would‌‌not‌‌be‌‌a‌‌good‌‌idea‌‌to‌‌use‌‌their‌‌names‌‌in‌‌place‌‌of‌‌ctor‌‌and‌‌dtor.‌  ‌
 ‌
 ‌
 ‌
We‌‌have‌‌introduced‌‌a‌‌common‌‌superclass‌‌Object‌‌for‌‌all‌‌our‌‌classes‌‌and‌‌we‌  ‌
have‌‌given‌‌it‌‌some‌‌functionality‌‌that‌‌simplifies‌‌implementing‌‌selector‌‌functions.‌  ‌
classOf()‌‌inspects‌‌an‌‌object‌‌and‌‌returns‌‌a‌‌non-zero‌‌pointer‌‌to‌‌its‌‌class‌‌description.‌  ‌
9‌  ‌

This‌‌permits‌‌the‌‌following‌‌implementation‌‌for‌‌delete():‌  ‌
void‌‌delete‌‌(void‌‌*‌‌_self)‌ 
{‌  ‌
if‌‌(_self)‌  ‌
free(dtor(_self));‌  ‌
}‌  ‌
void‌‌*‌‌dtor‌‌(void‌‌*‌‌_self)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌class‌‌=‌‌classOf(_self);‌ 
assert(class‌‌—>‌‌dtor);‌  ‌
return‌‌class‌‌—>‌‌dtor(_self);‌ 
}‌  ‌
new()‌‌must‌‌be‌‌implemented‌‌very‌‌carefully‌‌but‌‌it‌‌works‌‌similarly:‌  ‌
void‌‌*‌‌new‌‌(const‌‌void‌‌*‌‌_class,‌‌...)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌class‌‌=‌‌_class;‌  ‌
struct‌‌Object‌‌*‌‌object;‌  ‌
va_list‌‌ap;‌  ‌
assert(class‌‌&&‌‌class‌‌—>‌‌size);‌  ‌
object‌‌=‌‌calloc(1,‌‌class‌‌—>‌‌size);‌  ‌
assert(object);‌  ‌
object‌‌—>‌‌class‌‌=‌‌class;‌  ‌
va_start(ap,‌‌_class);‌  ‌
object‌‌=‌‌ctor(object,‌‌&‌‌ap);‌  ‌
va_end(ap);‌  ‌
return‌‌object;‌  ‌
}‌  ‌
We‌‌verify‌‌the‌‌class‌‌description‌‌and‌‌we‌‌make‌‌sure‌‌that‌‌we‌‌can‌‌create‌‌a‌‌zero-filled‌  ‌
object.‌‌Then‌‌we‌‌initialize‌‌the‌‌class‌‌description‌‌of‌‌the‌‌object‌‌and‌‌we‌‌are‌‌ready‌‌to‌‌let‌  ‌
the‌‌normal‌‌selector‌‌ctor()‌‌find‌‌and‌‌execute‌‌the‌‌constructor:‌ 
void‌‌*‌‌ctor‌‌(void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌class‌‌=‌‌classOf(_self);‌ 
assert(class‌‌—>‌‌ctor);‌  ‌
return‌‌class‌‌—>‌‌ctor(_self,‌‌app);‌  ‌
}‌  ‌
There‌‌is‌‌perhaps‌‌a‌‌bit‌‌too‌‌much‌‌checking‌‌going‌‌on,‌‌but‌‌we‌‌have‌‌a‌‌uniform‌‌and‌  ‌
robust‌‌interface.‌  ‌
 ‌
 ‌
1.9‌‌Superclass‌‌Selectors‌  ‌
Before‌‌a‌‌subclass‌‌constructor‌‌performs‌‌its‌‌own‌‌initialization,‌‌it‌‌is‌‌required‌‌to‌‌call‌‌the‌  ‌
superclass‌‌constructor.‌‌Similarly,‌‌a‌‌subclass‌‌destructor‌‌must‌‌call‌‌its‌‌superclass‌  ‌
destructor‌‌after‌‌it‌‌has‌‌completed‌‌its‌‌own‌‌resource‌‌reclamation.‌‌When‌‌we‌‌are‌  ‌
implementing‌‌selector‌‌functions,‌‌we‌‌should‌‌also‌‌supply‌‌selectors‌‌for‌‌the‌‌superclass‌  ‌
calls:‌  ‌
void‌‌*‌‌super_ctor‌‌(const‌‌void‌‌*‌‌_class,‌  ‌
void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌superclass‌‌=‌‌super(_class);‌  ‌
assert(_self‌‌&&‌‌superclass‌‌—>‌‌ctor);‌  ‌
return‌‌superclass‌‌—>‌‌ctor(_self,‌‌app);‌  ‌
10‌  ‌

}‌  ‌
void‌‌*‌‌super_dtor‌‌(const‌‌void‌‌*‌‌_class,‌‌void‌‌*‌‌_self)‌  ‌
{‌‌const‌‌struct‌‌Class‌‌*‌‌superclass‌‌=‌‌super(_class);‌  ‌
assert(_self‌‌&&‌‌superclass‌‌—>‌‌dtor);‌  ‌
return‌‌superclass‌‌—>‌‌dtor(_self);‌  ‌
}‌  ‌
These‌‌selectors‌‌should‌‌only‌‌be‌‌called‌‌by‌‌a‌‌subclass‌‌implementation;‌‌therefore,‌‌we‌ 
include‌‌their‌‌declarations‌‌into‌‌the‌‌representation‌‌file‌‌and‌‌not‌‌into‌‌the‌‌interface‌‌file.‌  ‌
To‌‌be‌‌on‌‌the‌‌safe‌‌side,‌‌we‌‌supply‌‌superclass‌‌selectors‌‌for‌‌all‌‌dynamically‌‌linked‌  ‌
methods,‌‌i.e.,‌‌every‌‌selector‌‌has‌‌a‌‌corresponding‌‌superclass‌‌selector.‌‌This‌‌way,‌  ‌
every‌‌dynamically‌‌linked‌‌method‌‌has‌‌a‌‌simple‌‌way‌‌to‌‌call‌‌its‌‌superclass‌‌method.‌  ‌
Actually,‌‌there‌‌is‌‌a‌‌subtle‌‌trap‌‌luring.‌‌Consider‌‌how‌‌a‌‌method‌‌of‌‌an‌‌arbitrary‌  ‌
class‌‌X‌‌would‌‌call‌‌its‌‌superclass‌‌method.‌‌This‌‌is‌‌the‌‌correct‌‌way:‌  ‌
static‌‌void‌‌*‌‌X_method‌‌(void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌void‌‌*‌‌p‌‌=‌‌super_method(X,‌‌_self,‌‌app);‌  ‌
...‌  ‌
Looking‌‌at‌‌the‌‌superclass‌‌selectors‌‌shown‌‌above‌‌we‌‌see‌‌that‌‌super_method()‌‌in‌  ‌
this‌‌case‌‌calls‌  ‌
super(X)‌‌—>‌‌method(_self,‌‌app);‌  ‌
i.e.,‌‌the‌‌method‌‌in‌‌the‌‌superclass‌‌of‌‌the‌‌class‌‌X‌‌for‌‌which‌‌we‌‌just‌‌defined‌  ‌
X_method().‌‌The‌‌same‌‌method‌‌is‌‌still‌‌reached‌‌even‌‌if‌‌some‌‌subclass‌‌Y‌‌inherited‌  ‌
X_method()‌‌because‌‌the‌‌implementation‌‌is‌‌independent‌‌of‌‌any‌‌future‌‌inheritance.‌  ‌
The‌‌following‌‌code‌‌for‌‌X_method()‌‌may‌‌look‌‌more‌‌plausible,‌‌but‌‌it‌‌will‌‌break‌  ‌
once‌‌the‌‌method‌‌is‌‌inherited:‌  ‌
static‌‌void‌‌*‌‌X_method‌‌(void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌void‌‌*‌‌p‌‌=‌‌/*‌‌WRONG‌‌*/‌  ‌
super_method(classOf(_self),‌‌_self,‌‌app);‌  ‌
...‌  ‌
The‌‌superclass‌‌selector‌‌definition‌‌now‌‌produces‌  ‌
super(classOf(_self))‌‌—>‌‌method(_self,‌‌app);‌  ‌
If‌‌_self‌‌is‌‌in‌‌class‌‌X,‌‌we‌‌reach‌‌the‌‌same‌‌method‌‌as‌‌before.‌‌However,‌‌if‌‌_self‌‌is‌‌in‌‌a ‌ ‌
subclass‌‌Y‌‌of‌‌X‌‌we‌‌get‌  ‌
super(Y)‌‌—>‌‌method(_self,‌‌app);‌  ‌
and‌‌that‌‌is‌‌still‌‌X_method(),‌‌i.e.,‌‌instead‌‌of‌‌calling‌‌a‌‌superclass‌‌method,‌‌we‌‌get‌  ‌
stuck‌‌in‌‌a‌‌sequence‌‌of‌‌recursive‌‌calls!‌  ‌
 ‌
 ‌
1.10‌‌A‌‌New‌‌Metaclass‌‌—‌‌PointClass‌  ‌
Object‌‌and‌‌Class‌‌are‌‌the‌‌root‌‌of‌‌our‌‌class‌‌hierarchy.‌‌Every‌‌class‌‌is‌‌a‌‌subclass‌‌of‌  ‌
Object‌‌and‌‌inherits‌‌its‌‌methods,‌‌every‌‌metaclass‌‌is‌‌a‌‌subclass‌‌of‌‌Class‌‌and‌  ‌
cooperates‌‌with‌‌its‌‌constructor.‌‌We‌‌know‌‌How‌‌a‌‌simple‌‌subclass‌‌can‌‌be‌‌made‌‌by‌‌replacing‌‌ 
dynamically‌‌linked‌‌methods‌‌of‌‌its‌‌superclass‌‌and,‌  ‌
possibly,‌‌defining‌‌new‌‌statically‌‌linked‌‌methods.‌  ‌
We‌‌now‌‌turn‌‌to‌‌building‌‌classes‌‌with‌‌more‌‌functionality.‌‌As‌‌an‌‌example‌‌we‌  ‌
connect‌‌Point‌‌and‌‌Circle‌‌to‌‌our‌‌class‌‌hierarchy.‌‌These‌‌classes‌‌have‌‌a‌‌new‌‌dynamically‌‌linked‌‌method‌‌ 
draw();‌‌therefore,‌‌we‌‌need‌‌a‌‌new‌‌metaclass‌‌to‌‌accommodate‌  ‌
the‌‌link.‌‌Here‌‌is‌‌the‌‌interface‌‌file‌‌Point.h:‌  ‌
#include‌‌"Object.h"‌  ‌
11‌  ‌

extern‌‌const‌‌void‌‌*‌‌Point;‌‌/*‌‌new(Point,‌‌x,‌‌y);‌‌*/‌  ‌
void‌‌draw‌‌(const‌‌void‌‌*‌‌self);‌  ‌
void‌‌move‌‌(void‌‌*‌‌point,‌‌int‌‌dx,‌‌int‌‌dy);‌  ‌
extern‌‌const‌‌void‌‌*‌‌PointClass;‌‌/*‌‌adds‌‌draw‌‌*/‌  ‌
The‌‌subclass‌‌always‌‌includes‌‌the‌‌superclass‌‌and‌‌defines‌‌a‌‌pointer‌‌to‌‌the‌‌class‌  ‌
description‌‌and‌‌to‌‌the‌‌metaclass‌‌description‌‌if‌‌there‌‌is‌‌a‌‌new‌‌one.‌‌Once‌‌we‌‌introduce‌‌metaclasses,‌‌we‌‌ 
can‌‌finally‌‌declare‌‌the‌‌selector‌‌for‌‌a‌‌dynamically‌‌linked‌  ‌
method‌‌where‌‌it‌‌belongs:‌‌in‌‌the‌‌same‌‌interface‌‌file‌‌as‌‌the‌‌metaclass‌‌pointer.‌  ‌
The‌‌representation‌‌file‌‌Point.r‌‌contains‌‌the‌‌object‌‌structure‌‌struct‌‌Point‌‌with‌‌its‌  ‌
access‌‌macros‌‌as‌‌before,‌‌and‌‌it‌‌contains‌‌the‌‌superclass‌‌selectors‌‌together‌‌with‌‌the‌  ‌
structure‌‌for‌‌the‌‌metaclass:‌  ‌
#include‌‌"Object.r"‌  ‌
struct‌‌Point‌‌{‌‌const‌‌struct‌‌Object‌‌_;‌‌/*‌‌Point‌‌:‌‌Object‌‌*/‌  ‌
int‌‌x,‌‌y;‌‌/*‌‌coordinates‌‌*/‌  ‌
};‌  ‌
#define‌‌x(p)‌‌(((const‌‌struct‌‌Point‌‌*)(p))‌‌—>‌‌x)‌  ‌
#define‌‌y(p)‌‌(((const‌‌struct‌‌Point‌‌*)(p))‌‌—>‌‌y)‌  ‌
void‌‌super_draw‌‌(const‌‌void‌‌*‌‌class,‌‌const‌‌void‌‌*‌‌self);‌  ‌
struct‌‌PointClass‌‌{ ‌ ‌
const‌‌struct‌‌Class‌‌_;‌‌/*‌‌PointClass‌‌:‌‌Class‌‌*/‌  ‌
void‌‌(*‌‌draw)‌‌(const‌‌void‌‌*‌‌self);‌  ‌
};‌  ‌
The‌‌implementation‌‌file‌‌Point.c‌‌contains‌‌move(),‌‌Point_draw(),‌‌draw(),‌‌and‌  ‌
super_draw().‌‌These‌‌methods‌‌are‌‌written‌‌as‌‌before;‌‌we‌‌saw‌‌the‌‌technique‌‌for‌‌the‌  ‌
superclass‌‌selector‌‌in‌‌the‌‌previous‌‌section.‌‌The‌‌constructor‌‌must‌‌call‌‌the‌‌superclass‌‌constructor:‌  ‌
static‌‌void‌‌*‌‌Point_ctor‌‌(void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌struct‌‌Point‌‌*‌‌self‌‌=‌‌super_ctor(Point,‌‌_self,‌‌app);‌  ‌
self‌‌—>‌‌x‌‌=‌‌va_arg(*‌‌app,‌‌int);‌  ‌
self‌‌—>‌‌y‌‌=‌‌va_arg(*‌‌app,‌‌int);‌  ‌
return‌‌self;‌  ‌
}‌  ‌
 ‌
 ‌
One‌‌new‌‌idea‌‌in‌‌this‌‌file‌‌is‌‌the‌‌constructor‌‌for‌‌the‌‌metaclass.‌‌It‌‌calls‌‌the‌‌superclass‌‌constructor‌‌to‌‌ 
perform‌‌inheritance‌‌and‌‌then‌‌uses‌‌the‌‌same‌‌loop‌‌as‌  ‌
Class_ctor()‌‌to‌‌overwrite‌‌the‌‌new‌‌dynamically‌‌linked‌‌method‌‌draw():‌  ‌
static‌‌void‌‌*‌‌PointClass_ctor‌‌(void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌struct‌‌PointClass‌‌*‌‌self‌  ‌
=‌‌super_ctor(PointClass,‌‌_self,‌‌app);‌  ‌
typedef‌‌void‌‌(*‌‌voidf)‌‌();‌  ‌
voidf‌‌selector;‌  ‌
va_list‌‌ap‌‌=‌‌*‌‌app;‌  ‌
while‌‌((selector‌‌=‌‌va_arg(ap,‌‌voidf)))‌  ‌
{‌‌voidf‌‌method‌‌=‌‌va_arg(ap,‌‌voidf);‌  ‌
if‌‌(selector‌‌==‌‌(voidf)‌‌draw)‌  ‌
*‌‌(voidf‌‌*)‌‌&‌‌self‌‌—>‌‌draw‌‌=‌‌method;‌  ‌
}‌  ‌
return‌‌self;‌  ‌
12‌  ‌

}‌  ‌
Note‌‌that‌‌we‌‌share‌‌the‌‌selector/method‌‌pairs‌‌in‌‌the‌‌argument‌‌list‌‌with‌‌the‌‌superclass‌‌constructor:‌‌ap‌‌ 
takes‌‌whatever‌‌Class_ctor()‌‌returns‌‌in‌‌*‌‌app‌‌and‌‌starts‌‌the‌  ‌
loop‌‌from‌‌there.‌  ‌
With‌‌this‌‌constructor‌‌in‌‌place‌‌we‌‌can‌‌dynamically‌‌initialize‌‌the‌‌new‌‌class‌  ‌
descriptions:‌‌PointClass‌‌is‌‌made‌‌by‌‌Class‌‌and‌‌then‌‌Point‌‌is‌‌made‌‌with‌‌the‌‌class‌  ‌
description‌‌PointClass:‌  ‌
void‌‌initPoint‌‌(void)‌  ‌
{‌  ‌
if‌‌(!‌‌PointClass)‌  ‌
PointClass‌‌=‌‌new(Class,‌‌"PointClass",‌  ‌
Class,‌‌sizeof(struct‌‌PointClass),‌  ‌
ctor,‌‌PointClass_ctor,‌  ‌
0);‌  ‌
if‌‌(!‌‌Point)‌  ‌
Point‌‌=‌‌new(PointClass,‌‌"Point",‌  ‌
Object,‌‌sizeof(struct‌‌Point),‌  ‌
ctor,‌‌Point_ctor,‌  ‌
draw,‌‌Point_draw,‌  ‌
0);‌  ‌
}‌  ‌
Writing‌‌the‌‌initialization‌‌is‌‌straightforward:‌‌we‌‌specify‌‌the‌‌class‌‌names,‌‌inheritance‌  ‌
relationships,‌‌and‌‌the‌‌size‌‌of‌‌the‌‌object‌‌structures,‌‌and‌‌then‌‌we‌‌add‌  ‌
selector/method‌‌pairs‌‌for‌‌all‌‌dynamically‌‌linked‌‌methods‌‌defined‌‌in‌‌the‌‌file.‌‌A‌‌zero‌  ‌
completes‌‌each‌‌argument‌‌list.‌  ‌
For‌‌now,‌‌initPoint()‌‌is‌‌added‌‌to‌‌the‌‌interface‌‌in‌‌Point.h‌‌and‌‌the‌‌function‌‌must‌‌definitely‌‌be‌‌called‌  ‌
before‌‌we‌‌can‌‌make‌‌points‌‌or‌‌subclasses.‌‌The‌‌function‌‌is‌‌interlocked‌‌so‌‌that‌‌it‌‌can‌  ‌
be‌‌called‌‌more‌‌than‌‌once‌‌—‌‌it‌‌will‌‌produce‌‌exactly‌‌one‌‌class‌‌description‌‌PointClass‌  ‌
and‌‌Point.‌  ‌
 ‌
 ‌
 ‌
As‌‌long‌‌as‌‌we‌‌call‌‌initPoint()‌‌from‌‌main()‌‌we‌‌can‌‌reuse‌‌the‌‌test‌‌program‌‌Points‌  ‌
$‌‌points‌‌p ‌ ‌
"."‌‌at‌‌1,2‌  ‌
"."‌‌at‌‌11,22‌  ‌
We‌‌can‌‌remove‌‌the‌‌ugly‌‌code‌‌in‌‌the‌‌constructor‌‌. ‌ ‌
static‌‌void‌‌*‌‌Circle_ctor‌‌(void‌‌*‌‌_self,‌‌va_list‌‌*‌‌app)‌  ‌
{‌‌struct‌‌Circle‌‌*‌‌self‌‌=‌‌super_ctor(Circle,‌‌_self,‌‌app);‌  ‌
self‌‌—>‌‌rad‌‌=‌‌va_arg(*‌‌app,‌‌int);‌  ‌
return‌‌self;‌  ‌
}‌  ‌
Of‌‌course,‌‌we‌‌need‌‌to‌‌add‌‌an‌‌initialization‌‌function‌‌initCircle()‌‌to‌‌be‌‌called‌‌from‌  ‌
main()‌‌before‌‌circles‌‌can‌‌be‌‌made:‌  ‌
void‌‌initCircle‌‌(void)‌  ‌
{‌  ‌
if‌‌(!‌‌Circle)‌  ‌
{‌‌initPoint();‌  ‌
13‌  ‌

Circle‌‌=‌‌new(PointClass,‌‌"Circle",‌  ‌
Point,‌‌sizeof(struct‌‌Circle),‌  ‌
ctor,‌‌Circle_ctor,‌  ‌
draw,‌‌Circle_draw,‌  ‌
0);‌  ‌
}‌  ‌
}‌  ‌
Because‌‌Circle‌‌depends‌‌on‌‌Point,‌‌we‌‌call‌‌on‌‌initPoint()‌‌before‌‌we‌‌initialize‌‌Circle.‌  ‌
All‌‌of‌‌these‌‌functions‌‌do‌‌their‌‌real‌‌work‌‌only‌‌once,‌‌and‌‌we‌‌can‌‌call‌‌them‌‌in‌‌any‌  ‌
order‌‌as‌‌long‌‌as‌‌we‌‌take‌‌care‌‌of‌‌the‌‌interdependence‌‌inside‌‌the‌‌function‌‌itself.‌  ‌
 ‌
 ‌
1.11‌‌Summary‌  ‌
Objects‌‌point‌‌to‌‌their‌‌class‌‌descriptions‌‌which,‌‌for‌‌the‌‌most‌‌part,‌‌contain‌‌pointers‌  ‌
to‌‌dynamically‌‌linked‌‌methods.‌‌Class‌‌descriptions‌‌with‌‌the‌‌same‌‌set‌‌of‌‌method‌  ‌
pointers‌‌constitute‌‌a‌‌metaclass‌‌—‌‌class‌‌descriptions‌‌are‌‌objects,‌‌too.‌‌A‌‌metaclass,‌  ‌
again,‌‌has‌‌a‌‌class‌‌description.‌  ‌
Things‌‌remain‌‌finite‌‌because‌‌we‌‌start‌‌with‌‌a‌‌trivial‌‌class‌‌Object‌‌and‌‌with‌‌a‌‌first‌  ‌
metaclass‌‌Class‌‌which‌‌has‌‌Object‌‌as‌‌a‌‌superclass.‌‌If‌‌the‌‌same‌‌set‌‌of‌‌methods‌‌— ‌ ‌
constructor,‌‌destructor,‌‌comparison,‌‌and‌‌display‌‌—‌‌can‌‌be‌‌applied‌‌to‌‌objects‌‌and‌  ‌
class‌‌descriptions,‌‌then‌‌the‌‌metaclass‌‌description‌‌Class‌‌which‌‌describes‌‌the‌‌class‌  ‌
description‌‌Object‌‌also‌‌describes‌‌itself.‌  ‌
A‌‌metaclass‌‌constructor‌‌fills‌‌a‌‌class‌‌description‌‌and‌‌thus‌‌implements‌‌binary‌  ‌
inheritance,‌‌the‌‌destructor‌‌returns‌‌zero‌‌to‌‌protect‌‌the‌‌class‌‌description‌‌from‌‌being‌  ‌
destroyed,‌‌the‌‌display‌‌function‌‌could‌‌show‌‌method‌‌pointers,‌‌etc.‌‌Two‌‌class‌  ‌
descriptions‌‌are‌‌the‌‌same‌‌if‌‌and‌‌only‌‌if‌‌their‌‌addresses‌‌are‌‌equal.‌  ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
 ‌
14‌  ‌

REFERENCES‌  ‌
[ANSI]‌‌American‌‌National‌‌Standard‌‌for‌‌Information‌‌Systems‌‌—‌‌Programming‌  ‌
Language‌‌C‌‌X3.159-1989.‌  ‌
[AWK88]‌‌A.‌‌V.‌‌Aho,‌‌B.‌‌W.‌‌Kernighan‌‌und‌‌P.‌‌J.‌‌Weinberger‌‌The‌‌awk‌‌Programming‌  ‌
Language‌‌Addison-Wesley‌‌1988,‌‌ISBN‌‌0-201-07981-X.‌  ‌
 ‌
AUTHOR‌  ‌
Guruprasad‌‌Davangave‌  ‌
Software‌‌Engineer‌  ‌
Computer‌‌Science‌‌and‌‌Engineering.‌‌[2021]‌  ‌
 ‌

You might also like