Advertisement

Storage layout for polymorphic objects

July 21, 2012

Dan_Saks-July 21, 2012

Adding at least one virtual function to a class alters the storage layout for all objects of that class type.

Earlier this year, I explained how virtual functions support dynamic function call binding in C++. (See "Virtual Functions in C++," Embedded.com, April 2012, www.eetimes.com/4370404). This month, I'll begin to explain how C++ compilers typically implement virtual functions by explaining how using virtual functions affects the storage layout for objects.

As before, 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. All of the shapes also have common attributes, such as position, and outline and fill colors. Each derived class adds some linear or angular distance(s) to characterize the physical extent of the shape. For example, a circle has a radius, a rectangle has a height and a width, and a triangle has two sides and an angle.

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:
    coordinates position;
    color outline, fill;
};

(As at least one reader observed earlier, the shape class should probably be an abstract base class with pure virtual functions. I haven't covered that subject yet, and it's not relevant to this discussion. Ditto for virtual destructors.)

If the area and perimeter member functions weren't virtual, then the functions wouldn't occupy any storage within shape objects. The storage for a shape object would contain only the storage for the data members, as shown in Figure 1.


Figure 1: The storage layout of a shape object if class shape had no virtual functions.


However, shape's area and perimeter member functions are virtual. A class type, such as shape, with a least one virtual function is a polymorphic type. An object of polymorphic type is a polymorphic object. C++ compilers typically add a pointer to the storage layout of each polymorphic object. That pointer is commonly called a vptr ("VEE-pointer") and it points to a table of function pointers called a vtbl ("VEE-table").

C++ compilers don't create vptrs and vtbls unless needed. Thus, non-polymorphic types don't have vptrs and vtbls.

Each polymorphic type has its own vtbl. That vtbl contains one pointer for each virtual function in the class. For example, the shape class has two virtual functions, area and perimeter, so shape's vtbl contains one pointer to shape's area function and another to shape's perimeter function, as shown in Figure 2.

Figure 2: The storage layout for shape objects and shape's vtbl.

Figure 2 shows the vptr situated at the beginning of each shape. Some compilers place the vptr after the last data member, instead. Each compiler can do as it pleases, as long as every object of a given polymorphic type has the same storage layout.

Each derived class inherits all the data members of its base class. Inherited members must have the same offsets within the derived class as they do in the base class. (This is true for classes with only one base class. It may not be true for classes with multiple base classes--a complication that I'm going to ignore for now.)

A class derived from a polymorphic base class will be polymorphic as well, and it inherits the base class's vptr. The vptr must have the same offset in the base class subobject (the base class portion) of a derived class object as it does in a base class object.

For example, you can derive the circle class from shape as follows:
class circle: public shape {
public:
    circle(double r);       // constructor
    virtual double area() const;
    virtual double perimeter() const;
    ~~~
private:
    double radius;
};

The circle class is polymorphic and it defines its own versions of area and perimeter. The compiler generates a distinct vtbl for class circle. Except that each circle object has an additional data member, radius, the storage layout for circle objects, shown in Figure 3, is identical to the layout for shape objects.


Figure 3: The storage layout for circle objects and circle's vtbl.


As another example, here's class rectangle derived from shape:
class rectangle: public shape {
public:
    rectangle(double h, double w);
    virtual double area() const;
    virtual double perimeter() const;
    ~~~
private:
    double height, width;
};

Except that each rectangle object has additional data members height and width, the storage layout for rectangle objects, shown in Figure 4, is identical to the layout for shape objects.


Figure 4: The storage layout for rectangle objects and rectangle's vtbl.


I'll have more to say in future columns about how virtual functions work and how you can emulate them in C.

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...