Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

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?

like image 447
ctn Avatar asked Jan 25 '16 13:01

ctn


People also ask

Why is virtual base class constructor called first?

Any non-virtual bases are then constructed before the derived class constructor is called. If a derived class has one virtual base class and other non-virtual base class, then the constructor of the virtual base class will be constructed first even though it appears at second position in the declaration.

What is virtual base class why it is used?

Virtual base class in C++ Virtual base classes are used in virtual inheritance in a way of preventing multiple “instances” of a given class appearing in an inheritance hierarchy when using multiple inheritances. Need for Virtual Base Classes: Consider the situation where we have one class A .

Why do we use virtual base class in C++?

Virtual base class in C++ Virtual classes are primarily used during multiple inheritance. To avoid, multiple instances of the same class being taken to the same class which later causes ambiguity, virtual classes are used.

Can copy constructor be virtual?

No you can't, constructors can't be virtual.


4 Answers

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) {}
};
like image 101
Lightness Races in Orbit Avatar answered Sep 29 '22 12:09

Lightness Races in Orbit


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.

like image 42
Sam Varshavchik Avatar answered Sep 29 '22 11:09

Sam Varshavchik


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.

like image 41
Peter - Reinstate Monica Avatar answered Sep 29 '22 11:09

Peter - Reinstate Monica


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.

like image 43
DarioP Avatar answered Sep 29 '22 12:09

DarioP