C Inheritance
C Inheritance
Computer
Program-
Science of Computer Programming 00 (2019) 1–13
ming
Inheritance pattern in C
Nikolai G. Lehtinena,∗
a Birkeland Centre for Space Science, University of Bergen, Bergen, Norway
Abstract
We present a tutorial on how inheritance may be implemented in C programming language, which does not have built-in support
for object-oriented programming (OOP). The main points of this tutorial are:
1. OOP is just a set of patterns, as all other programming paradigms.
2. Inheritance is same as an interface.
3. The “base” class must have a handle to the “derived” class.
4. It is necessary to use the “virtual function table” (VFT) for class members.
Previously suggested programming patterns did not stress all of these points and thus lead to limited and suboptimal solutions,
e.g., multiple inheritance was not supported. We conclude that C is fully suitable for bug-free OOP which does not rely on obscure
language features, even though at expense of a few extra lines of code.
1 PROGRAM SUMMARY
2 Program Title: Example of inheritance implementation in C
3 Licensing provisions: CC0 1.0
4 Programming language: C
5 Supplementary material: c inheritance.zip (C code)
6 Current version: January 16, 2019
7 Nature of problem: Implementation of inheritance in C programming language
8 Solution method: Using appropriate design patterns
9 Additional comments including Restrictions and Unusual features: None
10 1. Introduction
11 C language has a long history but is still relevant and widely used today [1]. It is a simple but powerful language
12 [2]. It is used not only for low-level programming work which is impossible to perform in more advanced languages,
13 but also for big projects like CPython, consistently being among the top used computer languages [3]. Thus, we must
14 seriously look into implementing modern programming paradigms into C in a readable and extensible manner [4, 5].
15 The main idea of various programming techniques is to simplify the programmer’s job. Because a human cannot
16 hold more than seven (or a similar small number) things in its head simultaneously, some modularity or separation of
17 work into smaller pieces is needed.
∗ Corresponding author.
18 The point of programming languages is to prevent our poor frail human brains from being overwhelmed
19 by a mass of detail. [6]
20 There are various programming paradigms which describe such “modularity.” After thinking a bit, we realize that
21 every paradigm is just a coding pattern. Even subroutines used to be a pattern once, but now they are part of virtually
22 all programming languages [7]. Classes and objects in the object-oriented programming paradigm (OOPP) are also
23 patterns. Classes are modules, and may be implemented as such in C. It is not necessary to simulate all of the OO
24 features down to syntax: e.g., it is completely unnecessary to put function pointers inside structs in order to simulate
25 methods, as was suggested, e.g., in [8, sec. 34.4]. The methods can be functions in the same module as the struct in
26 question. In this paper, we adopt the following convention: instead of the usual C++ syntax object.function(arg)
27 where object belongs to a Class, we use C syntax ClassFunction(object,arg). Declarations of such functions are
28 in Class.h, and definitions are in Class.c (usually).
29 We are going to discuss the C implementation of the OOPP inheritance pattern. We must admit that we do not
30 claim complete originality of the ideas presented here. In particular, we draw on ideas presented in the implementation
31 of the Reactor pattern in [4, part 5]. We strongly recommend the book [4] to the reader, as it demonstrates the power of
32 C language and its competitiveness in the modern industry with languages that are allegedly more advanced because
33 they already have the OOPP set of patterns built into them.
34 We treat the “inheritance” pattern the same as an “interface.” It is often recommended that “derived” classes
35 should inherit only from abstract classes which thus serve as “interfaces.” In order to extend a concrete class (usually
36 for the purpose of code re-use), another pattern is usually recommended, namely “composition” [9]. The “base”
37 class is included as a member of the “derived” class and the method calls on the “derived” class are forwarded (or
38 delegated) to the “base.” Thus, we have to be very careful in order not to abuse the inheritance capabilities when they
39 are available.
40 Availability of these alternatives leads to different approaches to inheritance in different computer languages. E.g.,
41 Java, beside syntax for inheritance, also has separate syntax for interfaces. To avoid conflicting inherited implemen-
42 tations, it does not allow multiple inheritance. However, it still allows multiple interfaces [10].
43 Abusing inheritance may lead to a complicated or inconsistent code. An example of bad usage of subtyping
44 (inheritance), in our humble opinion, can even be found in the classical C++ book [11, Section 3.2.4]:
45 A smiley face is a kind of a circle.
46 We should check it against the Liskov substitution principle [12]:
47 Subtype Requirement: Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true
48 for objects y of type S where S is a subtype of T.
49 We find that the properties φ1 = “does not have eyes” and φ2 = “does not have a mouth” are valid for a “circle” but not
50 valid for a “smiley face.” Thus, a “smiley face” is not a subtype of a “circle.” In theory, this can be fixed if we define
51 the type “circle” to have a property ψ = “has something inside,” where the “something” is just “nothing” for a proper
52 circle but substituted for “mouth and eyes” for the “smiley face.” But when we write the “circle” class, are we really
53 going to have so much foresight as to guess that we should allow it to have something inside it? Maybe the class from
54 which the “smiley face” inherits should be called not “circle” but something like “round thing,” which leaves more
55 freedom for interpretation. Then both “circle” and “smiley face” can inherit from the “round thing.”
56 A similar noncompliance with Liskov substitution principle is committed in [13, ch. 4] where a subclass Circle is
57 derived from a base class Point. A circle is definitely not a point! If we have a collection of various shapes like circles
58 and rectangles, and we want to store them in an array with uniform access, is it really an array of points?
59 These examples show us that we should be careful when choosing inheritance over composition, and even the
60 giants of the computer world are not immune to errors. In Section 2.3 we provide a C example of a Smiley class
61 which does not inherit from Circle but delegates function SmileyGetRadius to a member circle.
62 Inheritance pattern implementation (as an interface) presented in this paper differs from many similar implemen-
63 tations that can be found on the internet. An important concept that we implement but which is completely ignored
64 by others, is that the interface (base) must have the handle of the (derived) object, in order to have access to object-
65 specific functions and members. Some authors [14, 15] go around this by using so-called type punning [16], i.e.,
66 implementing base and derived objects in such a way that they occupy the same space in memory, thus forming a kind
2
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 3
67 of a union. Type punning is widely used in projects, e.g., in GTK [17]. However, it intimately depends on how C
68 interprets structs and may be subject to bugs [18]. Moreover, type punning allows only a single base and is therefore
69 incompatible with multiple inheritance.
70 Unlike the type-punning approach [14, 15], we do not consider the “derived” objects to be objects of the “base”
71 class, so we never perform true typecasting between the derived and base objects. (However, some analogy to typecast-
72 ing is discussed in Section 3.) The difference in types is obvious if we think of the “base” in terms of an interface—the
73 real object, of course, is not of the interface class. Therefore, our approach is more logical.
75 Attached to this article, there is a C code example with the described implementation. The compilation instructions
76 are given in the comments in the beginning of file main.c. Below, we go through the details of what the code exactly
77 does.
78 Consider a program that deals with various planar shapes, like Circle, Rectangle, etc. One can find features
79 which may be attributed to all of the different kinds of shapes, e.g., the position on the plane (x, y) or color with which
80 the shape is drawn. The various shapes also have an area and a perimeter, which are calculated in a different manner
81 for different types. All of the common features may be put in a base class, or an interface, which we call Shape. The
82 Shape part provides a uniform way of accessing the instances of derived classes. For example, we can create an array
83 of Shapes which are actually interfaces to more complicated derived class instances. By iterating over this array, we
84 draw different shapes and calculate their areas and perimeters. An appropriate method will be called for each object
85 (another way to say in OOP terminology is that each object receives a message). Thus, we do not have to worry about
86 determining the objects’ respective classes during the iteration.
87 We do not include in the text of this paper the discussion of various functions which are present in the attached
88 code but should only be used for debugging, or to demonstrate bad programming practices. In the code, these parts
89 are accompanied by appropriate warnings.
102 // ”New” calls should match ”Delete” + ”Replace” calls to prevent memory leaks 2
105 We also provide an analog of the copy-assignment operator in C++ which replaces an old value with the new, thereby
106 deleting the old value:
107 void ShapeReplace(Shape *old_shapePtr, Shape new_shape); 1
108 In order to prevent memory leaks, all ShapeNew operations should match corresponding ShapeDelete or ShapeReplace
109 operations, which will be clear from the implementation details.
110 The user interface is provided by a set of functions. Here are object member access functions (getters and setters,
111 e.g. for coordinates):
3
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 4
117 We can also access class members (common for a whole base or derived class):
118 // .. and for class members 1
119 char *ShapeGetClassName(Shape shape); // to demonstrate a class data member, NOT to do any dispatch over subclasses! 2
120 int ShapeCounterOfThisType(Shape shape); // virtual class method (?) Determines class by an instance. 3
121 These functions determine the class of the given instance shape and return an appropriate class data member. If the
122 class is the base class (shape is a pure-base object), the first function returns "Shape" and the second function returns
123 the total number of interfaces (sum of the numbers of all derived plus pure-base objects). If the class is a derived class,
124 these functions correspondingly return an appropriate name (e.g., "Circle", "Rectangle" etc) and the total number
125 of relevant objects (circles, rectangles, etc).
126 It is possible to access class members for a given class, too. Such functions do not need an instance (object),
127 but the class name has to be specified explicitly in the name of the function. E.g., the counter of all Shapes may be
128 accessed with
129 // in Shape.h 1
139 The functions for area and perimeter calculation and drawing a shape are overloaded by subclasses so they can be
140 considered virtual functions. If shape is a pure-base object, the call to a virtual function causes an error, and if it is a
141 derived-class object, an appropriate overloaded function is called. The last function, ShapeTranslation, utilizes only
142 information available in the base class. Its code is thus fully provided by the base class Shape, and is not overloaded
143 by any of the subclasses. Therefore, it cannot be called a virtual function.
150 // 1. The access to the derived object, or equivalently, the object using the interface 3
151 void *instance; // must be void∗ because we don’t know its type yet 4
153 // 2. Object members (usually, data members which are non−virtual by their nature) 6
154 float x, y; 7
156 struct ShapeClassMembers *vft; // all interface methods collected in a virtual function table 9
157 } 10
158 The pointer instance provides the access to the derived object (or, equivalently, the object which uses the interface
159 Shape). Its type is not known in advance, so we have to use void *. Next, there are instance data members float x, y,
4
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 5
160 which are individual for each object. Finally, there is a field that provides access to class data members and methods
161 struct ShapeClassMembers *vft, which is called (mostly for historical reasons) a virtual function table (VFT, [20]).
162 If we want to track class data members which change their values, it is necessary to introduce VFT because these
163 cannot be stored in struct ShapeInterfaceT which is individual to each instance of a class. A VFT is common for
164 the whole class (base or derived) and stores all the class members. It is implemented as
165 struct ShapeClassMembers { 1
172 }; 8
173 There are class data members char *class_name, int obj_counter and methods area, perimeter, draw, destruct
174 . The methods are virtual functions, which become concrete functions when a derived class is defined, and are
175 accessed by the user through ShapeArea etc., provided in Shape.h. In principle, we can also have methods (func-
176 tions) which are specific to an instance (something like a derived singleton class), which have to be stored in struct
177 ShapeInterfaceT, but we do not have them in this example.
184 } 5
185 6
186 // ”New” calls like this should match ”Delete” + ”Replace” calls 7
189 ShapeReset(shape); 10
193 } 14
194 The object counter increment here shape->vft->obj_counter++ applies to the base class. Another advantage of using
195 VFT is that we do not need to copy all the class members and function pointers into each new object created, but only
196 copy the address of the singleton ShapeVFT.
197 This VFT stores the pure-base class members and methods:
198 static struct ShapeClassMembers ShapeVFT = { 1
205 }; 8
206 The placeholder functions VirtualShapeArea etc., are implemented in order to avoid segfaults if the virtual func-
207 tions are called by mistake. For example:
208 static float VirtualShapeArea(void *instance) { 1
5
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 6
211 } 4
212 Their output may be used for diagnostics instead of just terminating the execution. (Exiting on error is turned on
213 with make OPT=-DEXIT_ON_VIRTUAL_CALL when compiling the code.) If the Shape interface is not attached to any
214 concrete (derived) class, then the instance argument of these virtual functions must be NULL, so that we can check it as
215 an extra level of safety. (Checking for such internal consistencies is turned on with make OPT=-DCHECK_INTERNAL.)
216 The virtual functions are declared as static and are invisible outside the file.
217 The access functions available to the user are implemented as one-liners:
218 // Methods 1
222 5
223 // Data member access (getters and setters), making them available to the user 6
228 11
233 The usage of the instance pointer is completely hidden from the user. The only disadvantage of the VFT design
234 pattern that we see here is that we have to perform an extra operation of dereferencing and member access, namely
235 shape->vft->area etc., which would not be needed if area were part of the struct ShapeInterfaceT *shape. How-
236 ever, this is far outweighed by the fact that the variable class members (like the object counters) are impossible without
237 VFT.
238 An example of a non-virtual method (i.e. one that does not use shape->vft):
239 void ShapeTranslation(Shape shape, float dx, float dy) { 1
241 printf("Moving shape by (dx=%f, dy=%f) to the new position at (%f, %f)\n", dx, dy, shape->x, 3
242 shape->y);
243 } 4
244 The destructor of the interface Shape should take care of calling an appropriate destructor for the derived part of
245 the class and freeing the memory occupied by the interface itself. The destructors for the derived parts are hidden
246 from the user. The reason for this is discussed in Section 3. The destructor is implemented in the following way:
247 void ShapeDelete(Shape *shapePtr) { 1
248 // We don’t check if shapePtr is NULL, we should not use addresses to anything but Shape 2
252 if (inst) { 6
255 } // if inst==NULL, then it means that some other interface already destructed it 9
257 free(shape); 11
258 *shapePtr = NULL; // this way we can tell it does not point to anything anymore 12
259 } 13
260 The decrement applies to the base class because shape->vft is restored to point to ShapeVFT after destruction of the
261 derived object part. The Shape which is passed to it by reference is changed to NULL in order to indicate that the object
6
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 7
262 has been destructed and to ensure that any future attempts to use it will give an error instead of a segfault. There is
263 more discussion of safe programming practices in Section 3. It is not an error when ShapeDelete is called without a
264 derived part. Its absence could be due either to the fact that it was never created, or, in a more complicated case with
265 “multiple inheritance”, i.e., multiple interfaces, that it had been destructed by another interface.
266 Sometimes we need to replace an object, which includes the destruction of the old one. This is equivalent to the
267 C++ copy-assignment operator:
268 void ShapeReplace(Shape *old_shapePtr, Shape new_shape) { 1
269 ShapeDelete(old_shapePtr); 2
271 } 4
272 For each malloc, there should be a free. Thus, each ShapeNew operation should have a matching ShapeDelete or
273 ShapeReplace. This will be our safe programming rule in order to avoid memory leaks. More safety rules are
274 discussed in Section 3.
278 2
280 4
283 // Derived class constructor when the base ”shape” is already constructed. 7
284 // There is no need for matching destructor because the deletion of the 8
285 // derived part is done automatically when the interface (base) is deleted 9
291 The user-accessible Circle is an ADT, and the internals stored in the concrete data type (CDT) are not user-accessible.
292 The implementation is in file Circle.c. The derived object stores the base plus its own members:
293 #include "Shape.h" 1
299 }; 7
300 A simple access to the base-class part is provided by a one-liner which may be considered a typecasting operator
301 from the derived to the base class:
302 Shape CircleShape(Circle circle) { return circle->shape; } 1
303 The creation of the Circle object is done only after the underlying interface Shape has been created. It is imple-
304 mented in the following way:
305 Circle CircleCreate(Shape shape, float radius) { 1
7
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 8
315 shape->vft->obj_counter++; 11
320 } 16
321 The VFT of the base class is replaced in the creator by the class’s own VFT:
322 static struct ShapeClassMembers CircleVFT = { 1
329 }; 8
330 The increment shape->vft->obj_counter++ in the creator applies to the number of Circle objects because shape->
331 vft points to CircleVFT. Since the creator takes a base object and returns a derived object, in may be looked at as a
332 typecasting operator (which, however, takes additional arguments like radius).
333 We implement concrete functions CircleArea, CirclePerimeter, CircleDraw and CircleDestruct using the ap-
334 propriate signatures from the Shape interface. They are declared as static and are therefore not visible to the user
335 directly, but are accessed through Shape’s interface. For example:
336 // This destructs only the derived additions 1
341 shape->vft->obj_counter--; 6
343 free(circle); 8
344 // Keep ”shape”, this method destructs only the ”derived” stuff 9
345 } 10
346 As we said, this destructor is hidden from the user and is only called by ShapeDelete (see the code in the previous
347 Subsection). Notice the decrement shape->vft->obj_counter-- which only applies to the number of Circle objects
348 because shape->vft points to CircleVFT. An explicit destructor of a Circle may be dangerous because the shape
349 member inside it may have other references to it which cannot be updated to have a NULL value. It is necessary to have
350 only a single useable reference to each Shape object for safe programming, as discussed in Section 3.
351 We have access to the total number of instances of a given derived class:
352 int CircleCounter(void) { return CircleVFT.obj_counter; } 1
353 It is also possible to access it with int ShapeCounterOfThisType(Shape shape) if shape is an interface to a Circle.
354 Other derived classes implemented in the attached code are Rectangle (which is also rather simple, like Circle;
355 calculations of the area and perimeter have, of course, different implementations), and Smiley, about which in detail
356 in the next Subsection.
370 }; 10
371 (The bool type is defined in <stdbool.h>.) In Smiley.h we have, of course, the ADT definition typedef struct
372 SmileyCDT *Smiley.
373 Instead of inheriting the Circle methods, we delegate them to the member face. However, this is not always
374 possible. In the user-accessible Smiley.h we have
375 // Circle methods 1
378 In the implementation Smiley.c we have a one-liner delegating the functionality to a member:
379 float SmileyGetRadius(Smiley smiley) { 1
381 } 3
382 The code for the setter SmileySetRadius would not be, however, a simple one-liner like this because all the facial
383 elements need to be resized and re-positioned when the radius of the face is changed. Code re-use, which we get by
384 default with inheritance, is impossible here. This function is not yet implemented in the current version of the code,
385 and the reader may do it as an excercise.
386 In the constructor, we create each facial feature. When we create the eyes and the mouth, we just scale them by
387 the given overall size radius:
388 Smiley SmileyCreate(Shape shape, float radius) { 1
389 int i; 2
390 double x, y; 3
400 shape->vft->obj_counter++; 13
9
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 10
416 } 29
417 The facial features belonging to different classes (Circle for eyes and face and Rectangle for the mouth) have to be
418 separate fields of the struct SmileyCDT. However, their common interfaces of type Shape are all stored in the array
419 elements which allows us to iterate over them for drawing or other operations.
420 The methods to calculate the perimeter and area call on the appropriate methods for the elements of the face.
421 For example, to calculate the area we must subtract the areas of the holes (the mouth and the eyes, which have
422 isForeground[i]==false):
423 static float SmileyArea(void *instance) { 1
426 int i; 4
431 } 9
432 SmileyPerimeter, SmileyDraw and the destructor SmileyDestruct are implemented analogously, by cycling over all
433 facial features (see the code for details).
440 #define N 5 5
442 { 7
443 // ... 8
444 } 9
445 We defined N, the number of Shapes, to be five. Let us see what happens inside function main:
446 1. Creation
447 // ... variable declarations etc. 1
454 All the diverse Shapes are stored in the same array. Namely, we have one Circle, one Rectangle and two
455 uninitialized base Shapes. The purpose of not creating any concrete objects for shapes[3], shapes[4] is to test
456 error recovery when they are accessed by accident.
457 2. User access: drawing, calculating area and perimeter etc.
458 for (i = 0; i < N; i++) { 1
10
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 11
463 );
464 printf("This shape: area=%f, perimeter=%f\n", ShapeArea(shape), ShapePerimeter(shape)); 6
466 } 8
467 Virtual placeholder functions are called for Shapes that do not have concrete objects associated with them. As
468 discussed above, the output from them may be used for diagnostics of such errors.
469 3. Clean-up
470 for (i = 0; i < N; i++) { ShapeDelete(&(shapes[i])); } 1
471 The elements of the array shapes are now all NULL, in order to prevent future errors.
472 4. Access to class members
473 // ... 1
475 ShapeCounterOfThisType(shape),ShapeGetClassName(shape)); 3
476 // ... 4
479 ShapeCounterTotal(),CircleCounter(),RectangleCounter(),SmileyCounter()); 7
480 We emphasize that we never put the concrete objects together into an array. This would be impossible since all
481 the objects are of different types. Only the interfaces are uniform and may be vectorized.
482 Optionally, the object may be created first and then converted to its base class for additional operations. In that
483 case, the code would be:
484 // code from item 1. ‘‘Creation’’ above 1
487 We just have to make sure that all the Shapes are referred to only by a single variable. Here, the variable for the Shape
488 associated with circle is shapes[0]. Such a rule is discussed in Section 3.
11
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 12
508 5. Comparison with composition. One may say that we just implemented “composition” instead of “inheritance”
509 because the base/interface handle is included in the struct of the derived class. A big difference, however, is that
510 the base/interface class was written particularly with the intention of serving as such, i.e., for the developers to
511 be able to create the derived classes from it. One feature that reveals this intention is that a base/interface object
512 has access to the internals of the derived object through the shape->instance field. Thus, it is not completely
513 decoupled from the derived class. This access is necessary because when we treat all the diverse shape objects
514 as Shapes, we only have the handles of the interfaces and not of the derived objects themselves. This was shown
515 in the example above where they were all stored in an array.
516 6. Creation and destruction order. The general policy is that the derived object cannot exist without any of its
517 interfaces. Thus, the interfaces must be created first and then a derived object is created taking interfaces
518 as arguments as derived = DerivedCreate(base1,base2,...,arg). The destruction of an interface triggers
519 destruction of the derived object, for which no user-accessible destructor is available. The difference between
520 creators of interfaces (base objects) on one hand and derived objects on the other hand is obvious if we use
521 our naming conventions: BaseNew (with corresponding destructor BaseDelete and copy-assignment operator
522 BaseReplace) versus DerivedCreate (without a corresponding destructor).
523 This idea may be taken to extreme and all objects could be inherited from a class Object which would provide
524 an interface for destruction (we could as well give it a name Destructible but we are open to possibility that
525 this interface could provide more functionality). The instances of this class could be stored in a list, which is
526 separate for each scope in the program. At the beginning of a scope, the list is initialized. As the objects are
527 created, they are added to this list. At the end of the scope, a single call to a function could destruct all the items
528 of this list:
529 // ... we have also ”outside scope” 1
531 Scope this_scope = ScopeNew(); // initialized list of Objects in the beginning of the scope 3
532 // ... 4
533 Shape shape = ShapeCreate(this_scope, x, y); // an Object instance is created for ”shape” and is added to the 5
536 Shape shape0 = ShapeCreate(outside_scope, x0, y0); // will NOT be detructed at the end of this scope 7
537 // ... 8
538 ScopeDelete(this_scope); // ”shape” is destructed automatically, among other objects added to this scope 9
540 // ... 11
542 As we see, we can keep track of different scopes. We do not even need to access individual objects (the Object
543 interface is thus completely private) unless we want to delete them in the middle of the scope. We have not
544 implemented such a model yet.
545 7. Safe programming. The user should keep only a single copy of each interface object. It is a pointer which is set
546 to NULL by BaseDelete. If there is a copy of it, the copy will not be set to NULL and the user may try to delete
547 it again, which will lead to a segmentation fault. If the user needs to use the same interface object in several
548 different places in his code, it is recommended to use multiple pointers to a single object instead. Since the base
549 object is a pointer itself (like Shape in the presented example), these multiple instances will be pointers to a
550 pointer.
551 An example of such an error is given in the attached code. In file Circle.h, we declare a function void
552 CircleDelete(Circle *circlePtr) which is supposed to take care of destructing a Circle without first access-
553 ing its base-class part Shape. Since it operates on the Shape behind the curtains, we lose track of whether it is
554 still valid and there is a danger of trying to delete it again.
555 8. Multiple inheritance. It may be implemented by having multiple interfaces in the same derived object. The
556 presented pattern protects us from memory leaks if all calls to constructors BaseXNew are matched with calls to
557 corresponding destructors BaseXDelete, where BaseX (e.g., Base1, Base2, etc.) are names of interface classes.
558 The destructors of the derived objects, however, must be hidden from the user, and should be automatically
559 called by BaseXDelete. If there are many bases (interfaces), the destructors for each of them have to be called
12
N.G. Lehtinen / Science of Computer Programming 00 (2019) 1–13 13
560 explicitly. The first call destructs the derived part, while the following calls just silently skip this step since the
561 derived part is not present anymore.
562 9. Multi-stage inheritance. The presented pattern seems to allow multi-stage inheritance. E.g., in a two-stage
563 inheritance, the base class is a small interface, the first-level derived class (child) is an extended interface, and
564 the second-level derived class (grandchild) is a concrete class using the extended interface. This may be of
565 use, e.g., when the first-level class contains many small interfaces, and is thus an “interface to a collection of
566 interfaces.” One may recognize it as the Facade pattern [9]. In our implementation, since the interfaces store
567 a pointer to a child, it is possible to get the concrete object even from the grandparent (small interface). As
568 for memory management, only the base objects must be explicitly deleted. The BaseDelete of the base class
569 triggers destruction of the child object, which in turn triggers the destruction of the grandchild object.
570 10. Diamond inheritance. A possible difficulty with multiple interfaces in our implementation is that each interface
571 is an object so we cannot blend them together. I.e., if two interfaces have a method with the same signature,
572 these two methods will be separate in our code because they are called by different interface objects. Such a
573 situation would be desirable if the same-signature method is inherited from a common grandparent, i.e., we
574 have a diamond-shaped inheritance graph [10]. Finding a good way to deal with such a situation is a problem
575 in other programming languages, too, so we will not try to solve it here.
576 [1] B. Klemens, 21st Century C, 2nd Edition, O’Reilly Media, Inc., 2015.
577 [2] C. Adamson, Punk rock languages: A polemic (2011).
578 URL http://bit.ly/punk-lang
579 [3] Tiobe index.
580 URL https://www.tiobe.com/tiobe-index/
581 [4] A. Tornhill, Patterns in C: Patterns, Idioms and Design Principles, Leanpub, 2015.
582 URL https://leanpub.com/patternsinc
583 [5] P. Hintjens, Scalable C: Writing Large-Scale Distributed C, GitBook, 2016, in progress.
584 URL https://legacy.gitbook.com/book/hintjens/scalable-c/details
585 [6] P. Graham, Five questions about language design (2001).
586 URL http://www.paulgraham.com/langdes.html
587 [7] P. Norvig, Design patterns in dynamic programming (1996).
588 URL http://norvig.com/design-patterns/design-patterns.pdf
589 [8] B. Meyer, Object-Oriented Software Construction, 2nd Edition, Prentice Hall, 1997.
590 [9] E. Gamma, R. Helm, R. Johnson, J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley Profes-
591 sional, 1994.
592 [10] GeeksforGeeks. [link].
593 URL https://www.geeksforgeeks.org/java-and-multiple-inheritance/
594 [11] B. Stroustrup, The C++ Programming Language, 4th Edition, Pearson Education, Inc., 2013.
595 [12] B. H. Liskov, J. M. Wing, A behavioral notion of subtyping, ACM Trans. Program. Lang. Syst. 16 (6) (1994) 1811–1841. doi:10.1145/
596 197320.197383.
597 [13] A. T. Schreiner, Object-Oriented Programming With ANSI-C, Lulu, 2011.
598 URL https://www.cs.rit.edu/~ats/books/ooc.pdf
599 [14] D. Saks, Alternative idioms for inheritance in C (2013).
600 URL https://www.embedded.com/electronics-blogs/programming-pointers/4411013/Alternative-idioms-for-inheritance-in-C
601 [15] Quantum Leaps, LLC, Application note: Object-oriented programming in C (2018).
602 URL https://www.state-machine.com/doc/AN_OOP_in_C.pdf
603 [16] Wikipedia. [link].
604 URL https://en.wikipedia.org/wiki/Type_punning
605 [17] StackExchange, Implement inheritance in C (2016).
606 URL https://softwareengineering.stackexchange.com/questions/338926/implement-inheritance-in-c
607 [18] M. Gallagher, Type punning isn’t funny: Using pointers to recast in C is bad (2008).
608 URL https://www.cocoawithlove.com/2008/04/using-pointers-to-recast-in-c-is-bad.html
609 [19] S. Meyers, Effective Modern C++, 1st Edition, O’Reilly Media, Inc., 2017, 11th release.
610 URL http://oreilly.com/catalog/errata.csp?isbn=9781491903995
611 [20] Wikipedia. [link].
612 URL https://en.wikipedia.org/wiki/Virtual_method_table
13