Alternative idioms for inheritance in C

April 01, 2013

Dan_Saks-April 01, 2013

C doesn't really support inheritance, but it offers alternative ways to mimic inheritance.

For more than a year now, I've been explaining how to implement and use virtual functions in C.1,2 I showed how to emulate a polymorphic C++ class (a class with at least one virtual function) as a C structure that has an additional member commonly called a vptr (VEE-pointer). The vptr points to a table of function pointers called a vtbl (VEE-table).

Last November, I showed a way to initialize derived polymorphic objects.3 My colleague, Miro Samek, posted a comment suggesting a slightly different approach. Last month, I explained the difference between the two approaches in detail.4

I had originally planned to explain why I prefer my approach to Miro's. After some very helpful exchanges with Miro, I realized that my implementation doesn't have the interface that I'd like it to have. So this month I'm going back up a bit and present yet another variation on how to implement inheritance and virtual functions in C.

Once again, my sample classes represent an assortment of two-dimensional geometric shapes such as circle, rectangle, and triangle, all derived from a common base class called shape. I recently suggested that the shape class should be an abstract class—one for which you can't, or at least shouldn't, create objects.5 To simplify this discussion, I'll assume the shape class is not abstract.

In C++, the shape implementation requires just a single class. In C, it requires two structures: one for the shape data members and one for the corresponding shape_vtbl. The C declarations for the shape "class" look like:

// shape.h - a C base class for shapes
~~~

typedef struct shape shape;
typedef struct shape_vtbl shape_vtbl;

struct shape_vtbl {
    double (*area)(shape const *me);
    double (*perimeter)(shape const *me);
};

struct shape {
    shape_vtbl *vptr;
    color outline, fill;
};

void shape_construct(shape *me, color o, color f);
double shape_area(shape const *me);
double shape_perimeter(shape const *me);

Each shape_vtbl member corresponds to a virtual function in the shape class. In this case, the shape class has two virtual functions: area and perimeter. It also has one non-virtual function: shape_construct, a constructor.

The function declarations at the end of the header declare all of the shape member functions, even the virtual ones, as non-virtual functions. Each virtual function needs a non-virtual implementation so that the corresponding function pointer in the shape_vtbl has something to which it can point.

In C++, every (non-static) member function in a class such as shape has an implicitly declared parameter named this whose type is "pointer to shape" or "pointer to const shape". A call such as ps->area() passes ps as the value of the area function's this parameter.

C doesn't declare this parameters implicitly. In C, every shape "member" function needs an explicitly-declared parameter of type "pointer to shape". (In some cases, it's "pointer to const shape", but I won't belabor that anymore.) In the shape.h header, I declared that pointer as the first parameter of each member function. In my previous articles, I named those parameters s (for shape). I could have called them this, but then my code would not compile as C++. In his examples, Miro Samek called the pointers me. For this article, I've adopted his convention. Otherwise, this shape class is pretty much as I presented in previous articles.

In C, each derived class, such as circle, requires two more structures: one for the circle data members and one for the corresponding circle_vtbl. The circle structure "inherits" the data members of the shape structure. That is, the initial portion of the circle structure should have all the data members in the same order as they appear in the shape structure. The simplest way to ensure this is to use "inheritance by composition"—to define a base class object as the first member of the derived class, as in:

typedef struct circle circle;
struct circle {
    shape base;     // the base class subobject
    double radius;
};

That is what I did in previous articles.



Footnotes:
1. Saks, Dan, "Virtual Functions in C++," Embedded.com, April 4, 2012. www.embedded.com/4370404.
2. Saks, Dan, "Virtual functions in C," Embedded.com, August 8, 2012. www.embedded.com/4391967.
3. Saks, Dan, "Initializing derived polymorphic objects," Embedded.com, November 15, 2012. www.embedded.com/4401463.
4. Saks, Dan, "Implementing a derived class vtbl in C," Embedded.com, February 13, 2013. www.embedded.com/4406901.
5. Saks, Dan, "Pure virtual functions," Embedded.com, December 12, 2012. www.embedded.com/4403313.

< Previous
Page 1 of 2
Next >

Loading comments...

Parts Search Datasheets.com

KNOWLEDGE CENTER