12

Is there a (practical) way to by-pass the normal (virtual) constructor calling order?

Example:

class A
{
    const int i;

public:
    A()
      : i(0)
    { cout << "calling A()" << endl; }

    A(int p)
      : i(p)
    { cout << "calling A(int)" << endl; }
};

class B
    : public virtual A
{
public:
    B(int i)
      : A(i)
    { cout << "calling B(int)" << endl; }
};

class C
    : public B
{
public:
    C(int i)
      : A(i), B(i)
    { cout << "calling C(int)" << endl; }
};

class D
    : public C
{
public:
    D(int i)
      : /*A(i), */ C(i)
    { cout << "calling D(int)" << endl; }
};


int main()
{
    D d(42);
    return 0;
}

Output:

calling A()
calling B(int)
calling C(int)
calling D(int)

What I want to have is something like:

calling A(int)
calling B(int)
calling C(int)
calling D(int)


As you see, there is virtual inheritance involved, which leads the constructor of D to call the constructor of A first, but since no parameter is provided, it calls A(). There's the const int i that needs initialisation, so I've got a problem.

What I'd like to do is to hide the inheritance details of C, that's why I'm looking for a way to avoid calling A(i) in D's (and every derived) constructor's initialisation list. [edit] In this specific case, I can assume there are only non-virtual single-inheritance child classes of C (as D is one). [/edit]

[edit]

Virtual base classes are initialized before any non-virtual base classes are initialized, so only the most derived class can initialize virtual base classes. – James McNellis

That's exactly the point, I don't want the most derived class to call the virtual base class constructor. [/edit]

Consider the following situation (not represented in the code example above):

  A
 / \
B0  B1
 \ /
  C
  |
  D  

I understand why C has to call the ctor of A (ambiguity) when you instantiate C, but why does D have to call it when instantiating D?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
dyp
  • 38,334
  • 13
  • 112
  • 177
  • 1
    I don't believe your code example matches with the output you provide. Are you sure you instanciate d with the instruction "D d;" ? – Benoît Aug 16 '10 at 16:46
  • sry, I forgot the parameter.... it's D d(42) now. Thanks. – dyp Aug 16 '10 at 16:51
  • Ok, that seems more fair :-) May i ask why you want to use the "dreaded diamond" architecture ? Can't you reorganize your code in some other way ? – Benoît Aug 16 '10 at 17:12
  • ^^ yeah, that's always a possiblity. I'd choose to redesign when there's really no practicable way. – dyp Aug 16 '10 at 17:22
  • @Benoît Because the "diamond" is not "dreaded", it is a very fine tool. – curiousguy May 13 '17 at 17:26

4 Answers4

7

Unfortunately, you will always have to call the virtual base classes constructor from the most derived class.

This is because you are saying that the virtual base is shared between all classes that derive from it for the instance of the object. Since a constructor may only be called once for a given instaniation of an object, you have to explicitly call the constructor in the most derived class because the compiler doesn't know how many classes share the virtual base (paraphrased (probably poorly) from The C++ Programming Language 3rd edition, section 15.2.4.1). This is because the compiler will start from the most base class's constructor and work to the most derived class. Classes that inherit from a virtual base class directly, will not, by the standard, call their virtual base classes constructor, so it must be called explicitly.

Smi
  • 13,850
  • 9
  • 56
  • 64
diverscuba23
  • 2,165
  • 18
  • 32
  • > Classes that inherit from a vitual base class directly, will not, by the standard, call thier vitual base classes constructor, so it must be called explicitly. That's right for class B, and then, class C could call it. But since C is not the very most derived class, D must call it. Any way to let C call it? D is directly derived from C with single non-virtual inheritance. – dyp Aug 16 '10 at 17:26
  • @DyP: Suppose that I latter add a class E that derives from C and then make D multiply derive from both C and E (even if I do not make C virtual) then which C calls the constructor for A? – diverscuba23 Aug 16 '10 at 17:37
  • sry for that, I edited the question. I can assume there is no multiple inheritance of C. – dyp Aug 16 '10 at 17:48
  • @DyP: For your specific case it there would be no problems with it, but the language is written for the general case which includes what I have given as an example. The easiest way to ensure that the constructor for a virtual base class is called exactly once for all possible cases is to make it so that the most derived class is responsible for calling it. – diverscuba23 Aug 16 '10 at 18:07
  • I agree and understand (now) why it was designed so. That's why I ask for a workaround of this rule (two-phase init or using inheritance or something) – dyp Aug 16 '10 at 18:40
  • "_Classes that inherit from a virtual base class directly_" Direct vs. indirect inheritance is an essential distinction for non virtual inheritance, not so much for virtual inheritance; you can argue that a virtual base is always "direct" (except for the purpose of access control where there are paths and access control is checked along paths). – curiousguy May 15 '18 at 21:45
2

I understand why C has to call the ctor of A (ambiguity) when you instanciate C, but why does D have to call it when instanciating D?

For the same reason that C has to call it. It's not an issue of ambiguity, it's the fact that A's constructior must be called only once (since it's a virtual base).

If you were hoping that C might be able to initialise A's constructor then what if class D were to inherit C and another class that ultimately inherits A?

Troubadour
  • 13,334
  • 2
  • 38
  • 57
  • > [...] what if class D were to inherit two C's [...]? Hadn't I then to virtually derive from C? – dyp Aug 16 '10 at 17:17
  • @DyP: Yes, fair point about two C's. I've edited that out of my answer. The point still stands though of course. – Troubadour Aug 16 '10 at 17:24
  • @DyP: Not nessisarily, if C can support having multiple copies of itself in an object, it need not be virtual. Granted its really rare that you would ever want a situation like that, but it is possible to do with the language. – diverscuba23 Aug 16 '10 at 17:27
  • "It's not an issue of ambiguity [...]", yes, I wanted to say it was nonsense if B called it when you instanciate a C, as C could be derived from a B0 and B1 (look at the diamond). My specific question deals with D, which is a single an non-virtual child to C, and other (non-virtual, single-inheritance) child classes of D. I edit the question. – dyp Aug 16 '10 at 17:44
0

Such are the rules. There are rules for overriding virtual functions and rules for constructing virtual base subobjects. Although both are very similar conceptually, they follow completely different rules, for a reason: overriding a virtual function is explicit. Calling a constructor is implicit for the default constructor.

Virtual functions in virtual base classes are only required to have one final overrider, an overrider that overrides all others overriders. (Virtual functions in non-virtual base classes cannot possibly have two overriders such that one does not override the other.)

But virtual base class constructors are always called from the most derived class, and usually in the implicit form of not bothering to mention the virtual base class in the ctor-init-list, as most classes designed to be used as virtual base classes are "pure interfaces" with no data members and no user initialization.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
-1

On parashift c++-faq-lite this issue is outlined.

Shamster
  • 2,092
  • 5
  • 24
  • 27
  • I believe the author was asking why you always had to call the virtual base classes constructor from the most derived class, and not a particular problem that he was having with the example he gave. The example was just to illistrate his question clearly. – diverscuba23 Aug 16 '10 at 17:17
  • @diverscuba23: It took me some searching to even understand what the issue was. Now I get it, but don't understand the situation that would lead to this. – Shamster Aug 16 '10 at 17:26