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++. 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 initialise each vptr properly. That initialisation 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 initialise 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 initialise it. The compiler does all that automatically. If you choose not to initialise 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 initialise the shape's vptr. The constructor also initializes the outline and fill members to their default values (which might be no initialisation 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 initialise 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 initialise 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.
文章评论(0条评论)
登录后参与讨论