Initializing derived polymorphic objects

November 15, 2012

Dan_Saks-November 15, 2012

Each class in a hierarchy of polymorphic objects should have a function that initializes its vptr properly.

For much of this year, I've been discussing polymorphic types and virtual functions. I showed how to implement virtual functions in C in a way that generates machine code similar to what you get with virtual function in C++.1 More specifically, 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 month, I showed how to initialize the vptr in base class objects. This month, I'll look at initializing derived class objects.2

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. The C++ definition for the shape base class looks in part like:

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

In C, the comparable declarations look like:

// shape.h - a C 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, color o, color f);
double shape_area(shape const *s);
double shape_perimeter(shape const *s);

#endif

As I showed last month, you can define the shape vtbl object in a source file that also defines the member functions of the shape "class":

 
// shape.c - a C base class for shapes

#include "shape.h"

~~~

static shape_vtbl the_shape_vtbl = {
    shape_area,
    shape_perimeter
};

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

In C++, the definition for a circle class derived from shape looks like:

 
class circle: public shape {
public:
    circle(double r, color o, color f); // constructor
    virtual double area() const;
    virtual double perimeter() const;
    ~~~
private:
    double radius;
};

Derivation defines an "is a" or "is a kind of" relationship between the derived and base class. That is, it lets you substitute a derived class object, such as a circle or rectangle, for a base class shape object. For example, given a C++ function such as:

 
void f(shape *p) {
    ~~~
    p->perimeter(); // virtual call to shape's perimeter
    ~~~
}

you can pass it a derived class object, as in:

 
circle c;
~~~
f(&c);              // pass a circle as a shape

and it computes the circle's perimeter correctly. With a little more effort, you can emulate this behavior in C. In C, you have to explicitly mention the vptr in virtual function calls, as in:

 
void f(shape *p) {
    ~~~
    p->vptr->perimeter(p);  virtual call to shape's perimeter
    ~~~
}

You also need an explicit cast to convert a "pointer to derived" into "pointer to base", as in:

 
circle c;
~~~
f((shape *)&c); // pass a circle as a shape

This substitution works (f will compute the circle's perimeter correctly) only if the vptr has the same offset in the derived class object as it does in a base class object. The easiest way to satisfy this requirement is to implement each derived class type in C as a structure whose first member has the base class type, as in:

 
// circle.h – a C class for circle derived from shape

#ifndef CIRCLE_H_INCLUDED
#define CIRCLE_H_INCLUDED

#include "shape.h"

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

void circle_construct(circle *c, double r, color o, color f);

#endif

The base member of the circle structure above includes all the members inherited from the shape base class, including vptr. The definition for the circle_construct function appears, along with the circle vtbl object, in a separate source file:

 
// circle.c - circle implementation
~~~
#include "circle.h"

double circle_area(circle const *c) {
    return PI * c->radius * c->radius;
}

double circle_perimeter(circle const *c) {
    return 2 * PI * c->radius;
}

typedef struct circle_vtbl circle_vtbl;
struct circle_vtbl {
    double (*area)(circle const *);
    double (*perimeter)(circle const *);
};

static circle_vtbl the_circle_vtbl = {
    circle_area,
    circle_perimeter
};

void circle_construct(circle *c, double r, color o, color f) {
    shape_construct(&c->base, o, f);
    c->base.vptr = (shape_vtbl *)&the_circle_vtbl;
    c->radius = r;
}

The circle_construct function implements behavior comparable to a C++ constructor. It calls the shape_construct function to initialize the base class part. However, shape_construct sets the vptr to point to shape's vtbl, which is correct for the base class shape, but not for the derived class circle. Thus, circle_construct needs to reassign the vptr to point to circle's vtbl.

< Previous
Page 1 of 2
Next >

Loading comments...

Most Commented

Parts Search Datasheets.com

KNOWLEDGE CENTER