Wednesday, September 3, 2014

Order of Construction in C++ for Polymorphic Classes

I came across a bit of a bug (or at least I thought it was) recently while programming. I thought I'd share the experience because it's a bit of a nuance of the C++ language and doesn't seem to fit what we normally expect C++ to do.

The issue was with polymorphism. I was creating a new class that derived from a base class. Part of the construction of the base class is to give it a pointer to it's owner. Upon construction, the base class would then call into the owner class, passing the this keyword to it. What I expected was to have all the vtable information of the derived class available to me. This is a key element to polymorphism that makes it such an incredible feature. However, when Owner began using the Derived object I had created (as a Base*), I found that it was calling only Base functions, not overloaded Derived functions.

Surely, I thought, I've discovered a bug in the compiler or something.

And then I decided to dig a bit deeper and try it out in a very controlled environment. This is the test I came up with.

#include <iostream>
using namespace std;

class Base
{
public:
    Base()
    {
        bool isDerived = GetIsDerived();
        if (isDerived)
        {
            cout << "Class is derived.";
        }
        else
        {
            cout << "Class is base.";
        }
    }
    virtual bool GetIsDerived() const { return false; }
};

class Derived : public Base
{
public:
    Derived() : Base() 
    {
        bool isDerived = GetIsDerived();
        if (isDerived)
        {
            cout << "Class is derived.";
        }
        else
        {
            cout << "Class is base.";
        }
    }
    virtual bool GetIsDerived() const { return true; }
};

int main(int argc, char** argV)
{
    Base base;

    Derived derived;
}


I have a Base class and a Derived class that extends Base. I then have a virtual function in Base called GetIsDerived(). This function always returns false, because in Base, that's true. In the Derived class, however, I overload it to always return true. In my entry point function, I create a new instance of Base on the stack. Sure enough, as you would expect, the first line printed is "Class is base." When I create the new Derived object on that stack, you'd think that the overloaded GetIsDerived() would run, but when within the Base constructor itself no vtables have been created yet, and as such, no overloaded functions will get callled. The total output is then this:

Class is base.
Class is base.
Class is derived.

So you see, when putting functionality in the constructor, rather than mere initialization, be careful what your functions are doing because until the object is fully constructed whatever is using it will treat it like the base and not like the derived class. The problem is even more difficult to see when you're giving someone else your this keyword and they begin using your pointer as the base, not the derived.

No comments:

Post a Comment