Design Con 2015

Initializing polymorphic objects

October 17, 2012

Dan_Saks-October 17, 2012

Virtual function calls work properly only if the vptrs and vtbls are initialized properly. C++ does this automatically, while C makes you do it manually.

On and off for the past year, I've been discussing polymorphic types and virtual functions. A couple of months ago, I showed how to implement virtual functions in C in a way that generates machine code similar to what you get with virtual functions in C++ (see "Virtual functions in C" Embedded.com, August 8, 2012). In particular, I showed that you can 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).

Of course, virtual function calls work only if you initialize each vptr properly. That initialization is my topic this month.

As in my prior articles, 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. In C++, the definition for the base class shape looks in part like:

class shape {
public:
    shape();                // constructor
    virtual double area() const;
    virtual double perimeter() const;
private:
    color outline, fill;
};

As at least one reader has observed, the virtual functions in the shape class should probably be pure virtual functions and the destructor should be virtual. I haven't covered these topics yet, and they're not crucial to this discussion.

You can implement a polymorphic shape type in C using the following declarations:

// shape.h - a base class for shapes

#ifndef SHAPE_H_INCLUDED
#define SHAPE_H_INCLUDED

typedef struct shape shape;

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

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

void shape_construct(shape *s);
double shape_area(shape const *s);
double shape_perimeter(shape const *s);

#endif

Each shape object has a vptr that must be initialized to point to a vtbl object. Every shape that actually is a shape (rather than a circle, rectangle or triangle) uses the same virtual functions, so the program needs only one vtbl object for every shape. In C, the vtbl object should be defined in the source file that also defines the member functions of the shape "class":

// shape.c - a base class for shapes

#include "shape.h"

double shape_area(shape const *s) {
    /* compute and return something */;
}

double shape_perimeter(shape const *s) {
    /* compute and return something */;
}

static shape_vtbl the_shape_vtbl = {
    shape_area,
    shape_perimeter
};

void shape_construct(shape *s) {
    s->vptr = &the_shape_vtbl;
}

The definition for the_shape_vtbl has an initializer that specifies a value for each function pointer.

The shape_construct function shown above initializes a shape object's vptr to point to the_shape_vtbl. You might want to augment that function to initialize other data members in a shape, as in:

void shape_construct(shape *s) {
    s->vptr = &the_shape_vtbl;
    s->outline = /* an appropriate default */;
    s->fill = /* an appropriate default */;
}

or as:

void shape_construct(shape *s, color o, color f) {
    s->vptr = &the_shape_vtbl;
    s->outline = o;
    s->fill = f;
}

In C++, you don't declare the vptr member and you don't write any code to initialize it. The compiler does all that automatically. If you choose not to initialize the outline and fill members explicitly, you could define the shape constructor in C++ as just:

shape::shape() {
}

The compiler automatically generates code in this constructor to initialize the shape's vptr. The constructor also initializes the outline and fill members to their default values (which might be no initialization at all).

Moreover, in C++, you don't even have to write the constructor. If you don't declare a shape constructor at all, the compiler will generate one for you that has the same effect as the one defined just above.

In C++, you can define a shape object such as:

void f() {
    shape s;
    ~~~

and the compiler automatically generates a call to the shape constructor to initialize the vptr. In C, you have to write that call explicitly, as in:

void f() {
    shape s;
    shape_construct(&s);
    ~~~

If you dynamically allocate a shape in C++ using:

void f() {
    shape *ps = new shape;
    ~~~

the compiler automatically generates a constructor call to initialize that shape's vptr. Again, in C you have to write that call explicitly, as in:

void f() {
    shape *ps = malloc(sizeof(shape));
    shape_construct(ps);
    ~~~

As I explained in that prior column, in C++, a call to the virtual area function applied to a shape looks exactly like a non-virtual call, as in:

ps->area();

In C, the equivalent call looks like:

ps->vptr->area(ps);

A properly-initialized vptr makes it happen.

I'll have more to say about virtual functions in future columns.

Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. For more information about Dan Saks, visit his website at www.dansaks.com. Dan also welcomes your feedback: e-mail him at dan@dansaks.com..

Loading comments...

Parts Search Datasheets.com

KNOWLEDGE CENTER