Understanding Inheritance

The Transition from C to C++

Few programmers will have missed the furore in recent years over objects and object oriented programming. Closer examination reveals terms beloved of academics such as encapsulation, inheritance and polymorphism. But what do these metaphors actually mean to workaday C programmers in terms of hands-on coding? Many have an excellent understanding of pointers, structures and dynamic allocation but feel the crux of the matter still eludes them.

In short, object orientation is a design approach. In C it is achieved in part by some clever but awkward tricks, and C++ (mercifully) automates the process. One major step in understanding this is to grasp the meaning of inheritance in C++ from a C perspective. From there one can see why sub-classing is one of the crucial enhancements to C that yielded C++.

Thumbnail image of the front cover of the 25th of September 1995 issue of Computing This article appeared originally in the September 25th 1995 issue of Computing, and was reviewed for Computing's Technical Advisory Board by Richard Badowski, a senior analyst programmer/contractor.

Exploiting Commonality

A fundamental design ethic in all programming is the exploitation of commonality. To use an example, any non trivial software system is virtually guaranteed to use one or more linked lists. Windowing interfaces manage lists of windows, text editors and wordprocessors maintain lists of lines of text. On this basis, one might declare two structures, as shown in the first example.


 struct Window
    {
    int     TopEdge;       /* Window coordinates and dimensions              */
    int     LeftEdge;
    int     Height;
    int     Width;

    Window *Prev;          /* A pointer to the previous window in the list   */
    Window *Next;          /* and a pointer to the next                      */

    };

 struct LineOfText
    {
    char        Text[100]; /* A string array containing the text             */

    LineOfText *Prev,      /* A pointer to the previous line in the document */
    LineOText  *Next;      /* and a pointer to the next                      */

    };
      

Both the window-management module and the text-editor module would contain functions for adding and removing Window structures from the window list, and for removing LineOfText structures from a document's list of lines respectively, as the next example illustrates.

Pausing momentarily, one can see that both structures contain pointers to previous and next list-members. Similarly, the equivalent text-editor function would duplicate the code for amending next and previous pointers, and for affixing a new item to the head of a list. Ideally, we would dispense with the duplication of list-handling code and capitalise on the fact that both windows and lines of text contain next and previous pointers.


 Window *TopWindow;  /* A global pointer to the first */
                     /* window in the display         */

 int WinModule_OpenWindow (Window *WinToOpen)
    {

    WinToOpen->Next = TopWindow;
    TopWindow->Prev = WinToOpen;
    TopWindow       = WinToOpen;

    /* Then display the window */

    }
      

List Manipulation

To achieve this in C we can declare our structures in a rather unusual fashion and implement a third module dealing entirely and exclusively with manipulating linked lists, as the next example shows.


 #define LIST_ITEM_CORE \
                        \
 struct ListItem *Prev; \
 struct ListItem *Next; \

 struct Window           /* Declare our Window structure     */
    {
    LIST_ITEM_CORE       /* Include the macro...             */

    int   TopEdge;       /* Then the Window specific members */
    int   LeftEdge;
    int   Height;
    int   Width;

    };

 struct LineOfText
    {
    LIST_ITEM_CORE

    char Text[100];

    };
      

Note how we do the same for the text-line structure. Now for a new window-opening function - our list-handling module requires the code shown in the next example.

The beauty of this approach is that any structure which contains next and previous pointers as its first members can be passed to ListModule_AddItem (). The casting coerces the compiler to treat the void ItemToAdd pointer as a ListItem pointer. Using the ListItem structure-declaration the compiler knows to generate code that refers to memory locations 0 and 5-bytes on from any address supplied, (assuming large memory-model compilation). We therefore have the capability to build lists of anything, as the contents of memory 9-bytes beyond the address represented by the ItemToAdd pointer is of no concern to the list handler.


 Window *TopWindow;  /* The owner of the first window in the list */

 int WinModule_OpenWindow (Window *WinToOpen)
    {

    ListModule_AddItem (&TopWindow, WinToOpen);

    }

 struct ListItem            /* Define a structure including the macro */
    {
    LIST_ITEM_CORE

    };


 int ListModule_AddItem (ListType *List, void *ItemToAdd)
    {

    ((ListItem *)ItemToAdd)->Next = ((ListType *)List)->FirstItem;

    List->FirstItem->Prev = ItemToAdd;
    List->FirstItem       = ItemToAdd;

    }
      

Inherited Attributes

The Window and LineOfText structures have therefore 'inherited' the attributes of a ListItem as both include the LIST_ITEM_CORE macro. Structures normally allow us to say "this thing is composed of...", we can now say "this thing is a kind of...", making a structure an extensible data-type.

Inheritance implies reuse, one can make a new structure a listable entity by including the LIST_ITEM_CORE macro at the very beginning of the declaration. Pointers to instances of the structure can then be passed to the list-handling functions as before. In addition we can reduce the number of source lines in a system or do more in the same space.

The list manipulation code can be said to be 'encapsulated' because any exclusively list-oriented operations occur only in that module instead of being spread across the system. This help us greatly, if there's a list-related bug we know precisely which module out of many contains it. Moreover, buying someone else's list-handling code means such bugs are their responsibility not ours.

The approach can be taken yet further. We could embed the LIST_ITEM_CORE macro itself in a WINDOW_CORE macro containing window structure members. That macro could be included at the beginning of a structure declaration for a menu making all menus a kind of window which are themselves listable entities.

The benefits of such a scheme are vast but it holds many dangerous pitfalls. The fact that the list module function accepts void pointers means we can inadvertently pass any kind of address to it, courting runtime disaster. Casts, by definition, preclude the benefits of automatic syntax-checking.

Readability and Responsibility

Similarly, wider use of the technique entails exponentially greater responsibility for the programmer regarding the order of macro inclusion. Moreover, readablity soon suffers and when you get it wrong, the profusion of obscure syntax-errors at compile time is overwhelming. Indeed in his book 'The C++ programming language' Bjarne Stroustrup regards every macro as evidence of a deficiency in the language, the design or the programmer[Stroustrup 160].

Some form of automation is obviously needed and to appreciate this is to see the need for an enhanced C. To understand the 'is a kind of' relationship possible with structures is to to appreciate the nature of the issue in C++.

Given this, the C++ equivalent of our example is as shown in the final example, which embodies data-type extensibility (inheritance), localisation of proceedure (encapsulation), automatic syntax-checking and more power for fewer lines.


 class ListItem
    {
    ListItem *Next;
    ListItem *Prev;

    };

 class Window : ListItem    // The ': ListItem ' replaces the macro
    {
    int TopEdge;
    int LeftEdge;
    int Height;
    int Width;

    };
      

References

The C++ Programming Language — Third Edition
B Stroustroup
Addison Wesley
ISBN 0 201 88954 4

Copyright © Richard Vaughan 1995