For much of this year, I've been discussing polymorphic types and virtual functions. Last month, I explained the memory layout of polymorphic objects. I also previously shown 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). This month, I'll look at initializing derived class objects.
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 ~~~ }