2

I've got following program:

#include<iostream> 
using namespace std; 
struct Base01{ 
    int m; 
    Base01():m(2){} 
    void p(){cout<<m<<endl;} 
}; 
struct Derived01:public Base01{ 
    Derived01():m(3){} 
}; 
struct Derived02:virtual public Base01{ 
    Derived01():m(4){} 
}; 
struct my: Derived01,Derived02{ 
    my():m(5){} 
}; 
int main(){ 
    return 0; 
} 

Both gcc/clang reports compilation error.

I just wish to know what's the language design consideration here, why derived class can only call base class ctor in initialization list, but cannot use base class members directly?

4 Answers 4

8

What you do in the constructor initializer list is initialization. It is something that has to be done only once in the lifetime of the object. In general case, that's what starts the objects lifetime.

Base class's constructor (which finished working before your derived class's constructor-proper became active) has already initialized all direct subobjects of the base class. It has already started their lifetimes. If you attempt to reach-in and initialize a direct subobject of base class from the derived class's constructor, that will obviously be the second initialization of the same object. This is completely unacceptable in C++. The language design generally does not allow you to initialize something the second time.

In your case the subobject in question has fundamental type int, so it is hard to see the harm in such "re-initialization". But consider something less trivial, like an std::string object. How do you suggest the derived class should "undo and redo" the initialization already performed by the base class? And while formally it is possible to do it properly, constructor initializer lists are not intended for that purpose.

In general case doing something like that would require a language feature that would allow user to tell base class's constructor something along the lines of "please, leave this subobject of yours uninitialized, I will reach-in and initialize it later from the derived class". However, C++ does not provide users with such capability. A vaguely similar feature exists in virtual base class initialization, but it serves a very specific (and different) purpose.

Sign up to request clarification or add additional context in comments.

2 Comments

A bit of a mistake on the ordering -- the derived constructor can control the parameters to the base subobject constructor, which clearly wouldn't be possible if the base constructor was invoked before the derived constructor got control. It might be better to talk about responsibility for construction. (Of course, double initialization is still a problem no matter which would happen first)
@BenVoigt Good point. I meant to say that base classes are constructed first and only after that "everything else" happens in derived class constructor. But of course, if language allowed users to refer to base class members in derived contsructor initializer list, the ordering rules would have been different (most likely). The matter of avoiding double initialization would still exist though, regardless of the ordering.
4

The proper way to do this in C++ is to pass that value to the base class constructor. Your Base01 class needs an additional constructor that takes the desired value for m. Something like this:

struct Base01{ 
    int m; 
    Base01():m(2){}

    // Added this:
    Base01(int mVal) : m(mVal) {} 

    void p(){cout<<m<<endl;} 
}; 

struct Derived01:public Base01{ 
    Derived01() : Base01(3) {}   // Calling base constructor rather than
                                 // initializing base member
};

struct Derived02:virtual public Base01{ 
    Derived01() : Base01(4){}    // Same here
};

struct my: Derived01,Derived02{ 
    my(): Base01(5){}            // And here.
}; 

As AnT said, you can't initialize twice--but you can set it up so that things are initialized the way you want in the first place by doing as above.

Comments

2

You certainly can use a base class member in the ctor-initializer list:

struct Base
{
    int x;
    Base(int x) : x(x) {}
};

struct Derived
{
    int y;
    Derived() : Base(7), y(x) {}
}

Here, the base member x appears in the initializer for the derived member y; its value will be used.

AnT has done a very nice job explaining why the ctor-initializer list can't be used to (re-)initialize members of base subobjects.

Comments

0

The fundamental language design considerations in this are separation of concerns (avoiding making a base class depend on its derived classes), and that a base class is responsible for initialising its own members (and any bases it has).

A related consideration is that members of the base class don't exist - as far as the derived class constructor is concerned - before the base class constructor completes. If an initialiser list of a derived class was able to reach in and initialise a base class member, then there are two possible consequences

  • If the base class constructor has not been invoked, its members will not exist when the derived class tries to initialise them.
  • If the base class constructor has been invoked, the member has been initialised. Initialisation (as distinct from assignment to reinitialise) happens once in the lifetime of an object, so it does not make sense to initialise it again.

Neither of these possibilities really make sense in practice, unless the base class is poorly designed (e.g. its constructors do not properly initialise its members). The sort of machinery needed so they might make sense (e.g. changing order of construction of base classes in a hierarchy, dependent on what member a derived class is trying to initialise) would make compiler machinery more complicated (e.g. being able to both control and track the order of construction of base class members, in case a derived class should choose to reach in), and also mean that the order of construction of classes would depend on derived classs). This would introduce a dependency of the base class behaviour (the means by which it is initialised) on derived classes.

The simpler means is for the base class to provide a constructor that properly initialised the member in question, and for the derived class constructor to invoke that base class constructor in its initialiser list. All of the above (hypothetical) considerations then go away.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.