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

坚强是说给别人听的谎言 提交于 2020-12-08 06:15:43

问题


My understanding, for instance reading this, is that the constructor of a derived class does not call its virtual base class' constructor.

Here is a simple example I made:

class A {
    protected:
        A(int foo) {}
};

class B: public virtual A {
    protected:
        B() {}
};

class C: public virtual A {
    protected:
        C() {}
};

class D: public B, public C {
    public:
        D(int foo, int bar) :A(foo) {}
};


int main()
{
    return 0;
}

For some reason, the constructors B::B() and C::C() are trying to initialize A (which, again in my understanding, should have already been initialized by D at this point):

$ g++ --version
g++ (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ g++ test.cpp
test.cpp: In constructor ‘B::B()’:
test.cpp:8:13: error: no matching function for call to ‘A::A()’
    8 |         B() {}
      |             ^
test.cpp:3:9: note: candidate: ‘A::A(int)’
    3 |         A(int foo) {}
      |         ^
test.cpp:3:9: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(const A&)’
    1 | class A {
      |       ^
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(A&&)’
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided
test.cpp: In constructor ‘C::C()’:
test.cpp:13:13: error: no matching function for call to ‘A::A()’
   13 |         C() {}
      |             ^
test.cpp:3:9: note: candidate: ‘A::A(int)’
    3 |         A(int foo) {}
      |         ^
test.cpp:3:9: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(const A&)’
    1 | class A {
      |       ^
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided
test.cpp:1:7: note: candidate: ‘constexpr A::A(A&&)’
test.cpp:1:7: note:   candidate expects 1 argument, 0 provided

I'm certain there is something very basic I misunderstood or am doing wrong, but I can't figure what.


回答1:


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.




回答2:


For some reason, the constructors B::B() and C::C() are trying to initialize A (which, again in my understanding, should have already been initialized by D at this point):

But what should compiler do if somebody constructs C solo? The final object D will call the constructor of A but you define constructor to C which implies that it can be constructed but the constructor is faulty cause it cannot construct A.




回答3:


Putting aside more complex class hierarchies, for any derived type there is exactly one copy of its virtual base. The rule is that the constructor for the most-derived type constructs that base. The compiler has to generate code to handle the bookkeeping for that:

struct B { };
struct I1 : virtual B { };
struct I2 : virtual B { };
struct D : I1, I2 { };

B b;   // `B` constructor initializes `B`
I1 i1; // `I1` constructor initializes `B` subobject
I2 i2; // `I2` constructor initializes `B` subobject

So far, it's easy enough to picture, since the initialization is done the same way as it would be if B was not a virtual base.

But then you do this:

D d; // which constructor initializes `B` subobject?

If the base wasn't virtual, the I1 constructor would initialize its B subject, and the I2 constructor would initialize its B subobject. But because it's virtual, there's only one B object. So which constructor should initialize it? The language says that the D constructor is responsible for that.

And the next complication:

struct D1 : D { };
D1 d1; // `D1` constructor initializes `B` subobject

So, along the way we've created five different objects, each with a virtual base of type B, and each with the B subobject being constructed from a different constructor.

Putting the responsibility on the most-derived type makes the initialization easy to understand and to visualize. There could have been other rules, but this one really is the simplest.



来源:https://stackoverflow.com/questions/64655841/why-do-the-constructor-of-the-derived-classes-want-to-initialize-the-virtual-bas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!