Thursday, November 29, 2007

Initialization lists and base class members

Just some thoughts upon a question that I ran across, raised about, why you could not initialize base class members in derived classes. Put it another way, why could you not use the base class data members in derived class' initialization lists. Simplest reason - the standards, the language rules don't allow it. But let's have some fun with the "what-if"s.

One reason that struck me almost as a first thought, that the data member could be private in which case, you won't have access to it in the derived class. But what if it is public or protected? Let us take an example:


    class Base
    {
    public:
        Base(){}
        Base(int member_) : member (member_){}
        int member;
        //other members
    };

    class Derived : public Base
    {
    public:
        Derived(int member_) : member(member_){}
        //other members
    };

    int main()
    {
        Derived derivedObject(10);
    }


You would have expected it to work since the base member is accessible in the derived class, it being public. After all, it is perfectly okay if you used it in the derived class constructor body! What is so special about initialization lists that only direct base classes, virtual bases and the containing class' data members can only appear?

You might think that the base class object might not have been allocated or does not exist at all while in the initialization list of the derived class and hence it is not allowed to do that. But that reasoning would be flawed. Why? For the following reason (quoting section 12.6.2 (5) from the standards):

Initialization shall proceed in the following order:

    — First, and only for the constructor of the most derived class as
described below, virtual base classes shall be initialized in the
order they appear on a depth-first left-to-right traversal of the
directed acyclic graph of base classes, where “left-to-right” is
the order of appearance of the base class names in the derived
class base-specifierlist.

    — Then, direct base classes shall be initialized in declaration order
as they appear in the base-specifier-list (regardless of the order
of the mem-initializers).

    — Then, non-static data members shall be initialized in the order
they were declared in the class definition (again regardless of
the order of the mem-initializers).

    — Finally, the body of the constructor is executed.

    [ Note: the declaration order is mandated to ensure that base and
member subobjects are destroyed in the reverse order of initialization.
    —end note ]

So, the base is already initialized before anything else happens in the initialization list. What could be the reason then? The reason probably is that it does not make sense! Once the base constructor has initialized the base member, the derived class initializing it does not make sense. How can one thing be initialized twice? The second time, it has to be an assignment.

But again, that holds true just for non-POD members. For the object being constructed in the above code, via default constructor of the base class, the POD member "member" remains uninitialized! It will have an indeterminate value. So, it won't be double initialization, would it? It would be initialized just once and that from the derived class constructor (initialization list).

Now, consider there were a further derived class that publicly derived from the above "Derived" class having the same member initialization syntax. Now, that makes it two. This could probably have been dealt with some complication set of rules but why add that logical overhead?

That is not all though. The *rules* can get more complex. The consideration of different access specifiers (private/protected), different inheritance types (private/protected), an explicit constuctor call that too initializes the member, virtual bases, different treatment for non-POD and POD types and what not. Things just start to get too complex and dirty if you allow that.

Simply speaking, base class members should be the base class' responsibility and derived class should only be concerned with the construction abstraction provided by the base classes in form of the base constructors and their initialization lists. Making things more coupled is always a sign of bad design choice where things just start to fall apart as soon as something changes. That is not good code.

To make things clear and simpler, it's best said and accepted that the standard does not allow it for initialization lists to take up the responsibility of initializing base class members, just the immediate bases, virtual bases and class' members.

Have fun... Cheers!

1 comment:

Leandro T. C. Melo said...

Abnegator (or should I call you Exterminator ;),

I was wondering about this subject and happened to arrive here. Nice post!

Regards,

Leandro T. C. Melo (ltcmelo)