19

In the following program the a member variable is not copied when B is virtually derived from A and instances of C (not B) are copied.

#include <stdio.h>

class A {
public:
    A() { a = 0; printf("A()\n"); }

    int a;
};

class B : virtual public A {
};

class C : public B {
public:
    C() {}
    C(const C &from) : B(from) {}
};

template<typename T>
void
test() {
    T t1;
    t1.a = 3;
    printf("pre-copy\n");
    T t2(t1);
    printf("post-copy\n");
    printf("t1.a=%d\n", t1.a);
    printf("t2.a=%d\n", t2.a);
}

int
main() {
    printf("B:\n");
    test<B>();

    printf("\n");

    printf("C:\n");
    test<C>();
}

output:

B:
A()
pre-copy
post-copy
t1.a=3
t2.a=3

C:
A()
pre-copy
A()
post-copy
t1.a=3
t2.a=0

Note that if B is normally derived from A (you delete the virtual) then a is copied.

Why isn't a copied in the first case (test<C>() with B virtually derived from A?

6
  • Apparently a new A is constructed when a C is copied which doesn't seem right to me (it should be copy-constructed, calling the default copy ctor, afaics, as happens when a B is copied). Commented Jan 25, 2016 at 13:22
  • @PeterA.Schneider: It's virtual inheritance... Commented Jan 25, 2016 at 13:23
  • 5
    It's a fundamental feature of virtual inheritance that the virtually derived class is responsible for explicitly calling each constructor of each of its bases. This is typically seen in the requirement to make 'duplicate' calls to the ctors of all base classes, even ones higher up the inheritance tree, which would normally be (and, in terms of written code, look like they are) called by an intermediate base. I'm pretty sure it's the same thing causing this. Commented Jan 25, 2016 at 13:24
  • 2
    Someone also posted this in an answer (before it was deleted for some reason): cprogramming.com/tutorial/virtual_inheritance.html
    – ctn
    Commented Jan 25, 2016 at 13:26
  • @ErikAlapää Who said a moderator deleted it? I have never heard of such a policy. Commented Jan 25, 2016 at 13:37

4 Answers 4

20

Virtual inheritance is a funny beast, in that copy construction isn't "inherited" in the same way that it would be normally. Your A base is being default-constructed because you are not explicitly copy-constructing it:

class C : public B {
public:
    C() {}
    C(const C &from) : A(from), B(from) {}
};
6
  • 1
    "copy construction isn't "inherited" in the same way that it would be normally": Do you have wording in the standard for that? I read there (12.8/15): "Virtual base class subobjects shall be initialized only once [but once! -pas] by the implicitly-defined copy/move constructor". Commented Jan 25, 2016 at 13:53
  • Edit: Hm, Seems to be not true, I didn't scroll down enough in the result window, I think. --- Also, for some reason, on ideone with C++14 (whatever gcc that is) the result is as one would expect(cf. ideone.com/Eeupgk). Commented Jan 25, 2016 at 13:55
  • @PeterA.Schneider: I spent a while trying to find the relevant wording after posting my answer, but gave up after a while. Feel free to edit it in when you've found it. :) Commented Jan 25, 2016 at 14:15
  • Well, It is fairly reasonable, as there are no possible instances of virtual class to copy-construct from. But, when I gave it a thought - yeah, I'd expect it to have such a constructor regardless. All derived inheritance trees are to be of the same "family" so the ability to copy-construct one from another is arguably desired.
    – loa_in_
    Commented Jan 25, 2016 at 17:11
  • 1
    @Deduplicator Normally the ctor of B would initialize the A which is the base of B. Here, the C ctor must do it (and fails, as you correctly diagnose). That is pretty unusual though. Normally C would inherit "all of B", including B's ability to initialize its (B's) subobjects. Commented Jan 25, 2016 at 19:30
15

The best way to understand virtual inheritance is by understanding that virtually-inherited classes are always subclassed by the most derived class.

In other words, the class hierarchy in the example ends up being, in a manner of speaking:

class A {
};

class B {
};

class C : public B, public A {
};

That's what, from some abstract viewpoint, is happening here. The "most derived", or the "top-level" class, becomes the direct "parent" of all virtual classes in its hierarchy.

Consequently, you are defining C's copy constructor, which copy-constructs B, however since A is no longer a subclass of B, nothing copy-constructs A, hence the behavior you're seeing.

Note that all of what I just said is applicable to the C class only. The B class, by itself, is derived from A as you'd expect. It's just that when you declare additional subclasses of a class with virtual superclasses, all virtual superclasses "float" to the newly-defined subclass.

4
  • 1
    Nice summary, but "parent" is an extremely confusing word to use for the most derived class... :P Parent/child aren't the best choices of words for inheritance at the best of times, but this one is opposite to most intuition! I get what you mean, though: that the most derived class is responsible for calling the constructors of all of its bases (even if those would normally be called by intermediate bases), thus 'giving life' to them... haha. Commented Jan 25, 2016 at 13:31
  • Not sure I'd go that far, but having re-read it, I think that in its attempt to aid understanding via analogies, it might actually confuse matters further Commented Jan 25, 2016 at 14:29
  • @underscore_d [I said "this is nonsense" in an earlier post, now deleted] After researching it I think that there is more truth in the post than I originally thought. Commented Jan 25, 2016 at 15:12
  • Now that I'm at home and read Stroustrup's TCPPL, I see that you were completely right; you basically paraphrased Stroustrup: "A virtual base is always considered a direct base of its most derived class." (21.3.5.1) Commented Jan 25, 2016 at 20:47
12

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.

10

Consider a diamond inheritance where you pass the C object to copy from, to both B1 and B2 ctors:

class A { public: int a };

class B1: virtual public A {};
class B2: virtual public A {};

class C: public B1, public B2 {
public:
    C(const C &from): B1(from), B2(from) {}
};

(see http://coliru.stacked-crooked.com/a/b81fad6cf00c664a).

Which one should initialize the a member? The first, the latter, both (in which order)? What if the B1 and B2 cctors initialize a in different ways?

This is why you need to call the A cctor explicitly, otherwise the members of the A class get default constructed.

What I really found funny is that defaulting the C cctor the compiler manages to copy the a member, but this is another question.

9
  • I didn't quite understand your last sentence. Commented Jan 25, 2016 at 13:30
  • If you take out the C constructors and leave them be defaulted they initialize the a member.
    – ctn
    Commented Jan 25, 2016 at 13:31
  • 1
    @DarioP: I see; you're referencing the fact that omitting initialisation of a virtual base from C's copy constructor will result in it being default-constructed, whereas omitting a user-provided C copy constructor entirely results in the virtual base being copy-constructed. That does seem somewhat inconsistent. Commented Jan 25, 2016 at 13:45
  • 1
    @LightnessRacesinOrbit I think it's ok because with everything omitted the compiler can decide by itself how to initialize the virtual base members, while if some of the B classes are given this becomes impossible. Just a funny fact ;)
    – DarioP
    Commented Jan 25, 2016 at 13:53
  • 1
    @DarioP: I suppose. I think I'd prefer it if omitting A's initialiser were ill-formed here, though. Commented Jan 25, 2016 at 14:15

Not the answer you're looking for? Browse other questions tagged or ask your own question.