Reputation: 2309
I have 3 classes in a hierarchy (call it A, B, and C) where B extends A and C extends B. Class A has a constructor that takes a single argument. The definition of C requires that A's constructor to be called, so I attempted to do that by creating a constructor in B. However, the compiler is telling me that C's constructor must initialize both A and B. That seems counter-intuitive to me because it should really be initialized just once.
Here's the code to better illustrate the issue that I'm facing:
#include <iostream>
struct A {
A(std::string name) : name_(name) {
std::cout << "A ctor called: " << name << std::endl;
}
std::string name_;
};
struct B : virtual public A {
// This constructor is required or else subclasses cannot be constructed properly
B(std::string name) : A(name) {
std::cout << "B ctor called: " << name << std::endl;
}
};
struct C : virtual public B {
// ERROR: constructor for 'C' must explicitly initialize the base class 'A' which does not have a default constructor
// C() : B("hey") {}
// ERROR: constructor for 'C' must explicitly initialize the base class 'B' which does not have a default constructor
// C() : A("hey") {}
// ok... but have to pass the same name twice & init'ed twice!
C() : A("wat"), B("hey") {
std::cout << "C ctor called" << std::endl;
}
// gcc reorders the constructor invocations...
// here it's written as B then A but it would be init'ed in the order of A then B
// C() : B("hey"), A("wat") {
// std::cout << "C ctor called" << std::endl;
// }
// ok... we can just pass a name but it's still init'ed twice!
// C(std::string name) : B(name), A(name) {}
};
int main() {
C c;
std::cout << c.name_ << std::endl;
}
When I run the code, I got:
A ctor called: wat
B ctor called: hey
C ctor called
wat
My questions are:
Is there a cleaner way to write it such that I don't have to call into both A & B's constructor explicitly?
Why does the output shows hey
being set at a later time but the name_
field contains wat
(which was set earlier)?
Upvotes: 2
Views: 205
Reputation: 118425
A virtually-inherited class is always inherited by the "most derived class". Your class C
is (mostly) exactly equivalent to:
struct C : virtual public A, virtual public B {
For all intents and purposes C
inherits from A
, when it is the most-derived class, whether you explicitly declare it, as such, or not. That's what virtual inheritance means, in C++.
Every class's constructor is responsible for constructing all classes it inherits from. This includes virtually-inherited classes. Whether they are explicitly inherited, or not. Even though you have:
struct C : virtual public B {
Since class C
is still inheriting from A
(whether you like or not), all of its constructors must formally construct A
, unless A
has a suitable default constructor, in which case A
gets default constructed when C
is the most-derived class (more on that later).
Here's where this gets even more complicated. Let's say you do your job:
C::C(...) : A{ ... }, B{ ... } // The actual parameters are irrelevant
Let's say you now declare one of these things:
C an_instance_of_c{ ... };
You successfully invoked this constructor for C
, the "most derived class", and it obediently constructed A
and B
, as per your instructions.
Now let's say that you created D
that inherits from C
:
struct D : public C { ... }
Then you go ahead and construct a D
:
D::D(...) : A{ ... }, B{ ... }, C{ ... }
You've just figured out that D
is now responsible for constructing A
and B
, for the same exact reasons that I just explained. It's also responsible for constructing C
, of course.
Now let's say that the parameters to C
's constructor here end up invoking the same constructor that appears above.
Well, guess what, even though you wrote that constructor to construct A
, and B
, that constructor will actually everything else it does, except that. D
is now responsible for constructing both A
and B
, but even though the same C
constructor gets called, it does not do anything that's related to A
and B
. After all, A
and B
are already constructed by D
. But C
will construct everything else it otherwise constructs.
When you have a virtually-inherited class, every constructor you explicitly or implicitly define results in your C++ compiler automatically compiling code for two separate constructors: one that constructs all virtually-inherited classes, and one that does not. Your C++ compiler will also end up generating code to invoke the appropriate version of each constructor, when this class is either the "most derived class" that's getting constructed, or not.
You will occasionally encounter virtual inheritance getting bad-mouthed, due to all the extra complexity that it creates, and all the pitfalls and gotchas that result from that (such as even if you privately and virtually inherit from a class, a subclass can always publicly virtually inherit from the same class and have protected and public access to everything you think you privately inherited). All of that is true, of course, but if you fully understand how virtual inheritance works, and what it does, there's nothing wrong with using it, and it allows things to be done in C++ that cannot be done in any of its peers.
Upvotes: 1
Reputation: 349
In your explanation you didn’t say you are doing this virtually. After seeing your code, I went OMG. First of all, ask yourself “why do I need to derive virtually?” The chances are there is no good reason for it. If you think there is a good reason for it, the chances are there is not. If you still insist on deriving virtually, with no good reason, see: https://isocpp.org/wiki/faq/multiple-inheritance
Upvotes: 1