Why Must Virtual Base Classes Be Constructed by the Most Derived Class

Why must virtual base classes be constructed by the most derived class?

Because it avoids this:

class A {
public:
A(int) {}
};

class B0: virtual public A {
public:
B0(): A(0) {}
};

class B1: virtual public A {
public:
B1(): A(1) {}
};

class C: public B0, public B1 {
public:
C() {} // How is A constructed? A(0) from B0 or A(1) from B1?
};

Why do the constructor of the derived classes want to initialize the virtual base class in C++?

The constructor of virtual base is constructed. It is constructed conditionally. That is, the constructor of the most derived class calls the constructor of the virtual base. If - this is the condition - the derived class with virtual base is not the concrete class of the constructed object, then it will not construct the virtual base because it has already been constructed by the concrete class. But otherwise it will construct the virtual base.

So, you must correctly initialise the virtual base class in constructors of all derived classes. You simply must know that specific initialisation doesn't necessarily happen in case the concrete class is not the one which you are writing. The compiler doesn't and cannot know whether you will ever create direct instances of those intermediate classes, so it cannot simply ignore their broken constructors.

If you made those intermediate classes abstract, then the compiler would know that they are never the most concrete type and thus their constructor would not be required to initialise the virtual base.

(Why) Is virtual base class constructor call required in pure virtual derived class?

Design issues (which clearly exist here) aside, I fully agree with your reasoning that, because Left is an abstract class, no Left constructor will ever have to call any Base constructor and thus it is odd that it is required.

In fact, I tested your code with several compiler versions and it does compile just fine with gcc 7.1 and onwards, with clang 3.4.1 and onwards, and with all available msvc versions.

So, my assumption is, that this was just a bug in earlier compiler versions. Can anyone confirm this?

Also note, if you change virtual void leftsMethod() = 0; to virtual void leftsMethod() {}, so that Left is not abstract anymore, the error returns, even with the latest versions. And this makes perfectly sense, as now you could instantiate a Left instance and thus it would be up to Left's constructor to call one of Base's constructors.

Possible workaround

If you cannot switch to a newer compiler and if you cannot alter the implementation of Base, this may be a working solution for you:

Define a dummy instance of CustomType somewhere. Then, you can provide the "required" default constructors like this:

class CustomType{};

class Base
{
public:
Base( CustomType& obj ) : refObj_( obj ) {}
private:
CustomType& refObj_;
};

static CustomType dummy;

class Left : public virtual Base
{
protected:
Left() : Base(dummy) {}; // note here
public:
virtual void leftsMethod() = 0;
};

class Right : public virtual Base
{
protected:
Right() : Base(dummy) {}; // and here
public:
virtual void rightsMethod() = 0;
};

class Bottom : public Left, public Right
{
public:
Bottom( CustomType& obj ) : Base( obj ), Left(), Right() {}
virtual void leftsMethod() override {}
virtual void rightsMethod() override {}
};

void test()
{
CustomType c;
Bottom b(c);
}

This is just to make the compiler happy, your argument that these constructors will never call Base(dummy) still holds.

Note however, that Base should really have a virtual destructor! I don't know if you just didn't include it here for brevity or if it really doesn't have one. If it doesn't, it's a very bad idea to build a class hierarchy on top of it.

Why does using a virtual base class change the behavior of the copy constructor

The C++11 standard says in 12.6.2/10:

In a non-delegating constructor, initialization proceeds in the
following order:

— First, and only for the constructor of the most derived class (1.8), virtual base classes are 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 classes in the derived class
base-specifier-list.

— [direct base classes etc. ...]

This says it, basically -- the most derived class is responsible for initialization in whatever way it defines it (in the OP: it doesn't, which leads to default initialization). The subsequent example in the standard features a similar scenario as in the OP here, just with an int argument to the ctor; only the default ctor of the virtual base is called, because no explicit "mem-initializer" for the virtual base is provided in the most derived class.

Of interest, although nor directly applying here, is also 12.6.2/7:

A mem-initializer [the A() in a possible B(): A() {}. -pas] where the mem-initializer-id denotes a virtual base
class is ignored during execution of a constructor of any class that
is not the most derived class.

(I find that pretty tough. The language basically says "I don't care what you coded, I'm gonna ignore it." There are not so many places where it can do that, violating as-if.) That constructor of a not-most-derived-class would be B(). The sentence does not directly apply here because there is no explicit constructor in B, so there is no mem-initializer either. But although I could not find wording for that in the standard one must assume (and it is consistent) that the same rule applies for the generated copy constructor.

For completeness, Stroustrup says in "The C++ Programming Language" (4.ed, 21.2.5.1) about a most derived class D with a virtual base V down the road somewhere:

The fact that V wasn't explicitly mentioned as a base of D is irrelevant. Knowledge of a virtual base and the obligation to initialize it "bubbles up" to the most derived class. A virtual base is always considered a direct base of its most derived class.

That is exactly what Sam Varshavchik said in an earlier post.

Stroustrup then goes on to discuss that deriving a class DD from D makes it necessary to move V's intialization to DD, which "can be a nuisance. That ought to encourage us not to overuse virtual base classes."

I find it fairly obscure and dangerous that a base class stays uninitialized (well, more precisely: default-initialized) unless the most-derived class explicitly does something.

The author of the most-derived class must dive deep into
an inheritance hierarchy s/he may have no interest in or documentation about and cannot rely on e.g. the library s/he uses to do the right thing (the library can't).

I'm also not sure I agree with the rationale given in other posts ("which of the various intermediate classes should perform the initialization?"). The standard has a clear notion of the initialization order ("depth-first left-to-right traversal"). Couldn't it mandate that the first class encountered which virtually inherits from a base performs the initialization?

The interesting fact that the default copy ctor does initialize the virtual base is prescribed in 12.8/15:

Each base or non-static data member is copied/moved in the manner
appropriate to its type:

[...]

— otherwise, the base or member is
direct-initialized with the corresponding base or member of x.

Virtual
base class subobjects shall be initialized only once by the
implicitly-defined copy/move constructor (see 12.6.2).

In any event, because C is the most derived class it is C's (and not B's) responsibility to copy-construct the virtual base A.

In C++, what is a virtual base class?

Virtual base classes, used in virtual inheritance, is a way of preventing multiple "instances" of a given class appearing in an inheritance hierarchy when using multiple inheritance.

Consider the following scenario:

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

The above class hierarchy results in the "dreaded diamond" which looks like this:

  A
/ \
B C
\ /
D

An instance of D will be made up of B, which includes A, and C which also includes A. So you have two "instances" (for want of a better expression) of A.

When you have this scenario, you have the possibility of ambiguity. What happens when you do this:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

Virtual inheritance is there to solve this problem. When you specify virtual when inheriting your classes, you're telling the compiler that you only want a single instance.

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

This means that there is only one "instance" of A included in the hierarchy. Hence

D d;
d.Foo(); // no longer ambiguous

This is a mini summary. For more information, have a read of this and this. A good example is also available here.



Related Topics



Leave a reply



Submit