If I have a class inheritance relation like the following
a
/ \
b c
\ |
| d
\/ \
e f
\ /
g
Is the following the correct definition?
class A {};
class B: public virtual A {};
class C: public virtual A {};
class D: public C {};
class E: public B, public virtual D {};
class F: public virtual D {};
class G: public E, public F {};
I made both A and D virtually inherited because I assume each joint class need to be virtual.
Also I am not sure how C++ defines the constructor order of the above case. The link
https://isocpp.org/wiki/faq/multiple-inheritance#mi-vi-ctor-order says
The very first constructors to be executed are the virtual base
classes anywhere in the hierarchy. They are executed in the order they
appear in a depth-first left-to-right traversal of the graph of base
classes, where left to right refer to the order of appearance of base
class names.
After all virtual base class constructors are finished, the
construction order is generally from base class to derived class. The
details are easiest to understand if you imagine that the very first
thing the compiler does in the derived class’s ctor is to make a
hidden call to the ctors of its non-virtual base classes (hint: that’s
the way many compilers actually do it). So if class D inherits
multiply from B1 and B2, the constructor for B1 executes first, then
the constructor for B2, then the constructor for D. This rule is
applied recursively; for example, if B1 inherits from B1a and B1b, and
B2 inherits from B2a and B2b, then the final order is B1a, B1b, B1,
B2a, B2b, B2, D.
Note that the order B1 and then B2 (or B1a then B1b) is determined by
the order that the base classes appear in the declaration of the
class, not in the order that the initializer appears in the derived
class’s initialization list.
If so, is the order like?
A, D, B, C, D, F, G
I do not expect D is constructed before C. What is the correct constructor order?
When constructing an object of class G, the virtual base classes are initialized first. This results in the constructors for A and D being called. The constructor for D will construct it's base C object. Then the constructors for the non-base classes of G will be called, which would be E and F. The constructor for E will call the constructor for B. This gives the final order:
A C D B E F G
I added constructors that call printf to watch what happens (code available on Godbolt for the curious). The order I got, with both clang (version 6.0.0) and gcc (versions 6.4.0 and 7.3.0), is:
A
C
D
B
E
F
G
This matches what I'd expect. A and D have to be constructed first, but you can't construct a D without a C (imagine a more complex example where D's constructor called into C. Once all virtual inheritance has been satisfied, the constructors are called in the same order we expect.
Related
So I was wondering lately what the copy constructor of an class will look if we have a base class A that is being virtually inherited by B and C, where B and C are inherited by D and D is inherited by E.
So I am a bit confused. Should D's copy constructor still have the base class's (A) constructor in its initializing list or is that the job of E now? Am I correct that E could inherit D non-virtually?
The implicitly generated copy constructor works fine, but if you wanted to explicitly write it yourself it could look like:
D(D const &o): A(o), B(o), C(o) {}
E(E const &o): A(o), D(o) {}
For initialization purposes, virtual base classes behave as if they are a direct base of the most-derived object. Any initializer in a subobject is ignored (e.g. in my code for E, the initialization D(o) actually does not initialize the A despite the fact that A is listed in D's copy constructor).
This is the same whether or not E inherits D virtually.
Link to further reading
Yes, A should still be in the initializing list and E does not need to inherit D virtually. The order of construction should be:
A() --> B() --> C() --> D()
C++ FAQs item 20.05:
"Virtual base classes are special, their destructors are called at the
end of the most derived class' destructor (only)."
I dont really understand how this fits in with the typical:
"data member destructors first, then base class destructors" rule
How are virtual base classes special? I cannot tell what the above means :s
The key property of virtual base classes is that they always produce a single unique base subobject in any object of derived class. That's exactly what's special about virtual base classes and that makes them different from regular base classes, which can produce multiple subobjects.
For example, in this hierarchy
struct B {};
struct M1 : B {};
struct M2 : B {};
struct D : M1, M2 {}
there's no virtual inheritance. All bases are inherited using regular inheritance. In this case class D will contain two independent subobjects of type B: one brought in by M1, another - by M2.
+-> D <-+
| |
M1 M2
^ ^
| |
B B <- there are two different `B`s in `D`
The task of properly destructing all subobjects when destructing D is trivial: each class in the hierarchy is responsible of destructing its direct bases and only its direct bases. That simply means that destructor of M1 calls the destructor of its own B subobject, destructor of M2 calls the destructor of its own B subobject, while destructor of D calls the destructors of its M1 and M2 subobjects.
Everything works out nicely in the above destruction schedule. All subobjects get destructed, including both subobjects of type B.
However, once we switch to virtual inheritance things become more complicated
struct B {};
struct M1 : virtual B {};
struct M2 : virtual B {};
struct D : M1, M2 {}
Now there only one subobject of type B in D. Both M1 and M2 see and share the same subobject of type B as their base.
+-> D <-+
| |
M1 M2
^ ^
| |
+-- B --+ <- there is only one `B` in `D`
If we make a naive attempt to apply the previous destruction schedule to this hierarchy, we'll end up with the B subobject getting destructed twice: M1 calls the destructor of B subobject, and M2 calls the destructor of the very same B subobject.
That is, of course, completely unacceptable. Each subobject has to be destructed once and only once.
In order to solve this problem, when M1 and M2 are used as base subobjects of D, these are explicitly prohibited to call the destructor of their B subobject. The responsibility of calling the destructor of B is assigned to D's destructor. Class D, when used as a complete independent object (i.e. serves as a most derived class), knows that there's only one B in it and knows that the destructor of B has to be called only once. So, the destructor of class D will call the destructor of B for that unique base subobject of type B. Meanwhile, destructors of M1 and M2 will not even attempt to call the destructor of B.
That's how it works with virtual inheritance. And that what the rule you quoted says basically. The parts that says that virtual bases' destructors are called last simply means that each class'e destructor calls destructors for its direct regular base classes, and only after that, if necessary, it calls destructors of its virtual base classes (possibly indirect). In the above example, the destructor of D calls destructors of M1 and M2 and only after that it calls the destructor of B.
The entire paragraph of the book you are quoting was describing the order of destructors. Normally, in the class declaration, the order of the classes listed for inheritance determines the order of their construction, and then they are destructed in reverse order.
A virtual base class means virtual inheritance was used on it:
struct Base {};
struct D : virtual Base {};
struct D1 : D, virtual Base {};
struct D2 : virtual Base, D {};
ASCII art alert:
Base Base
| \ | \
/_\ \ | /_\
| \ | \
D /_\ | D
| / | /
/_\ / /_\ /_\
| / | /
D1 D2
The multiple inheritance collapses the diamond into a single line, in this case. But, the point is still illustrated. The order of inheritance for D1 and D2 doesn't matter. The order in which the destructors for D and Base are called will be the same for both of them.
What are the specific problems that are being avoided with the restrictions imposed by this clause 12.7p3 (see the first part of paragraph below)?
In the example shown in 12.7p3 (see below) why X(this) is considered defined? Is it because X is not in the path E C D B A ?
struct A { };
struct B : virtual A { };
struct C : B { };
struct D : virtual A { D(A*); };
struct X { X(A*); };
struct E : C, D, X {
E() : D(this), // undefined: upcast from E* to A*
// might use path E* - D* - A*
// but D is not constructed
// D((C*)this), // defined:
// E* - C* defined because E() has started
// and C* - A* defined because
// C fully constructed
X(this) { // defined: upon construction of X,
// C/B/D/A sublattice is fully constructed
}
};
Please find below the start of paragraph 12.7p3:
To explicitly or implicitly convert a pointer (a glvalue) referring to
an object of class X to a pointer (reference) to a direct or indirect
base class B of X, the construction of X and the construction of all
of its direct or indirect bases that directly or indirectly derive
from B shall have started and the destruction of these classes shall
not have completed, otherwise the conversion results in undefined
behavior.
Is it correct to say that the set of all direct and indirect bases of X, mentioned above don't include B, and because of this the code below is well defined, notwithstanding the fact that Base is a direct base of Derived and has yet not started?
struct Base{ Base(Base*); };
struct Derived : Base {
Derived() : Base(this) {};
};
What are the specific problems that are being avoided with the restrictions imposed by this clause 12.7p3 (see the first part of paragraph below)?
The problems have to do with the fact that, at some point, the virtual pointers (to virtual table or virtual base classes) must be initialized for the derived class. This is an initialization that occurs during the "initialization list" (i.e., before the constructor-body begins), usually right after the base class was constructed (e.g., after base class B is constructed, the virtual table pointer and possibly virtual base pointer is set for class X, and then the data members are initialized, and then the constructor's body begins). In general, doing an up-cast before those pointers are initialized will yield undefined behaviour, either because the pointer lookup required cannot be done for a virtual base, or the virtual table pointer will not be set correctly for the virtual functions to be called correctly.
In the example shown in 12.7p3 (see below) why X(this) is considered defined? Is it because X is not in the path E C D B A ?
Because the base class constructors are called in the order that they appear in the class declaration (i.e., struct E : C, D, X {). Because the base classes C and D are both constructed, their common virtual base class A is certainly also constructed. And because X does not inherit from class A, it means that there is a fully-constructed and unambiguous path when performing the cast from class E to class A at this point. That is why that line is well-defined.
Is it correct to say that the set of all direct and indirect bases of X, mentioned above don't include B, and because of this the code below is well defined, notwithstanding the fact that Base is a direct base of Derived and has yet not started?
I'm not sure I follow your explanation, but I can tell you that the code you showed is not well-defined. When calling the constructor of Base by casting Derived* this to Base*, the base-class object in the derived class is not yet constructed, and thus, the cast is undefined.
The only way that this code could make sense is if there were no virtual functions or virtual base classes involved in the hierarchy, in which case, such code would not be needed since the this pointer to the base class is already sent, implicitly, to the base-class constructor.
The US Air Force's JSF C++ coding standard requires that the virtual base class be declared for each derived class that accesses the virtual base.
For example, in the following hierarchy:
A
/ \
B1 B2
C1 C2
\ /
D
... the rule they impose in this standard (AV Rule 88.1, for reference), requires the classes to be declared like so:
class A;
class B1 : virtual A;
class B2 : virtual A;
class C1 : B1, virtual A;
class C2 : B2, virtual A;
class D : C1, C2, virtual A;
My questions are as follows:
Is this semantically different from only inheriting virtually in the declaration # B1/B2, and not specifying virtual A at each subsequent class declaration?
If it's semantically different, why would anyone /want/ to leave it off? It seems silly to me that you'd absolutely have to do this at each layer of inheritance since that adds a potential point of failure.
It semantically identical, since each derived class will have exactly one virtual base of type A. Mentioning the virtual base explicitly is quite nice, because the most-derived class constructs the virtual base (unlike what happens for non-virtual bases), and the construction order is important to keep in mind when writing the constructors of the derived classes.
I don't have a technical answer for (2). You don't have to do it, but it would be nice if you did. Like calling your parents, I suppose. As with many things, C++ doesn't force you to be reasonable.
Here http://www.parashift.com/c++-faq-lite/multiple-inheritance.html section [25.14] says
The very first constructors to be executed are the virtual base classes anywhere in the hierarchy.
I tried to verify it using following program:
A (pure virtual)
|
B
|
C
(virtual)/ \ (virtual)
E D
\ /
F
|
G (pure virtual)
|
H
each class has a c'tor and virtual d'tor. the output is as follows:
A
B
C
E
D
F
G
H
~H
~G
~F
~D
~E
~C
~B
~A
Press any key to continue . . .
but as per quote virtual base classes constructors should be executed first.
what did I understand wrong?
EDIT: To clear my question, As per my understanding this behaviour has nothing to do with whether a base class is virtual or not. but quote insists on Virtual Base class. am I clear or something fishy there?
Virtual base classes cannot be constructed if the classes they inherit from are not constructed first. So in your case, non-virtual base classes are constructed because the virtual ones depend on them: C can't be constructed until A and Bare. Therefore, A and B are indeed constructed before C, even though C is virtually inherited.