Calling Virtual Methods in Base Class Constructors

From RAD Studio
Jump to: navigation, search

Go Up to C++ and Object Pascal Models


Virtual methods invoked from the body of Delphi style base class constructors, that is, classes implemented in Object Pascal, are dispatched as in C++, according to the run-time type of the object. Because C++Builder combines the Object Pascal model of setting the run-time type of an object immediately, with the C++ model of constructing base classes before the derived class is constructed, calling virtual methods from base class constructors for Delphi style classes can have subtle side-effects. The impact of this is described below, then illustrated in an example of an instantiated class that is derived from at least one base. In this discussion, the instantiated class is referred to as the derived class.

Object Pascal Model

In Object Pascal, programmers can use the inherited keyword, which provides flexibility for calling base class constructors anywhere in the body of a derived class constructor. Consequently, if the derived class overrides any virtual methods that depend upon setting up the object or initializing data members, this can happen before the base class constructor is called and the virtual methods are invoked.

C++ Model

The C++ syntax does not have the inherited keyword to call the base class constructor at any time during the construction of the derived class. For the C++ model, the use of inherited is not necessary because the run-time type of the object is that of the current class being constructed, not the derived class. Therefore, the virtual methods invoked are those of the current class, not the derived class. Consequently, it is not necessary to initialize data members or set up the derived class object before these methods are called.

C++Builder Model

In C++Builder, the Delphi style objects have the run-time type of the derived class throughout calls to base class constructors. Therefore, if the base class constructor calls a virtual method, the derived class method is invoked if the derived class overrides it. If this virtual method depends upon anything in the initialization list or body of the derived class constructor, the method is called before this happens. For example, CreateParams is a virtual member function that is called indirectly in the constructor of TWinControl. If you derive a class from TWinControl and override CreateParams so that it depends on anything in your constructor, this code is processed after CreateParams is called. This situation applies to any derived classes of a base. Consider a class C derived from B, which is derived from A. Creating an instance of C, A would also call the overridden method of B, if B overrides the method but C does not.

Be aware of virtual methods like CreateParams that are not obviously called in constructors, but are invoked indirectly.

Example: calling virtual methods

The following example compares C++ and Delphi style classes that have overridden virtual methods. This example illustrates how calls to those virtual methods from base class constructors are resolved in both cases. MyBase and MyDerived are standard C++ classes. MyRTLBase and MyRTLDerived are Delphi style classes descended from TObject. The virtual method what_am_I() is overridden in both derived classes, but is called only in the base class constructors, not in derived class constructors.

# include  <sysutils.hpp >
# include  <iostream.h >
# include  <stdio.h>

// non-Delphi style classes
class MyBase {
public:

    MyBase() {
        what_am_I();
    }

    virtual void what_am_I() {
        cout << "I am a base" << endl;
    }
};

class MyDerived : public MyBase {
public:

    virtual void what_am_I() {
        cout << "I am a derived" << endl;
    }

};

// Delphi style classes
class MyRTLBase : public TObject {
public:

    __fastcall MyRTLBase() {
        what_am_I();
    }

    virtual void __fastcall what_am_I() {
        cout << "I am a base" << endl;
    }
};

class MyRTLDerived : public MyRTLBase {
public:
    virtual void __fastcall what_am_I() {
        cout << "I am a derived" << endl;
    }
};

int main(void) {
    MyDerived d; // instantiation of the C++ class
    MyRTLDerived *pvd = new MyRTLDerived; // instantiation of the Delphi style class
    getchar();
    return 0;

}

The output of this example is

I am a base
I am a derived

because of the difference in the run-time types of MyDerived and MyRTLDerived during the calls to their respective base class constructors.

Constructor initialization of data members for virtual functions

Because data members may be used in virtual functions, it is important to understand when and how they are initialized. In Object Pascal, all uninitialized data is zero-initialized. This applies, for example, to base classes whose constructors are not called with inherited. In standard C++, there is no guarantee of the value of uninitialized data members. The following types of class data members must be initialized in the initialization list of the class constructor:

  • References
  • Data members with no default constructor


Nevertheless, the value of these data members or those initialized in the body of the constructor, is undefined when the base class constructors are called. In C++Builder, the memory for Delphi style classes is zero-initialized.

A virtual function that relies upon the value of member variables initialized in the body of the constructor or in the initialization list may behave as if the variables were initialized to zero. This is because the base class constructor is called before the initialization list is processed or the constructor body is entered. The following example illustrates this:

 # include  <sysutils.hpp>

class Base : public TObject {

public:
    __fastcall Base() {
        init();
    }

    virtual void __fastcall init() {
    }

};

class Derived : public Base {

public:
    Derived(int nz) : not_zero(nz) {
    }

    virtual void __fastcall init() {

        if (not_zero == 0)
            throw Exception("not_zero is zero!");
    }

private:
    int not_zero;
};

int main(void)

{
    Derived *d42 = new Derived(42);
    return 0;

}

This example throws an exception in the constructor of Base. Because Base is constructed before Derived, not_zero has not yet been initialized with the value of 42 passed to the constructor. Be aware that you cannot initialize data members of your Delphi style class before its base class constructors are called.

See Also