16

I'm a bit confused about how virtual base classes work. In particular, I was wondering how the constructor of the base class gets called. I wrote an example to understand it:

#include <cstdio>
#include <string>
using std::string;

struct A{
    string s;
    A() {}
    A(string t): s(t) {}
};

struct B: virtual public A{
    B(): A("B"){}
};

struct C: virtual public A {};

struct D: public B, public C {};

struct E: public C, public B {};

struct F: public B {};

int main(){
    D d;
    printf("\"%s\"\n",d.s.c_str());
    E e;
    printf("\"%s\"\n",e.s.c_str());
    F f;
    printf("\"%s\"\n",f.s.c_str());
    B b;
    printf("\"%s\"\n",b.s.c_str());
}

Which outputs

""
""
""
"B"

I wasn't sure what would happen in the first two cases, but for the third one at least I was expecting the output to be "B". So now I'm just confused. What are the rules for understanding how the constructor of A gets called?

Cœur
  • 37,241
  • 25
  • 195
  • 267
pythonic metaphor
  • 10,296
  • 18
  • 68
  • 110

2 Answers2

10

There is always just one constructor call, and always of the actual, concrete class that you instantiate. It is your responsibility to endow each derived class with a constructor which calls the base classes' constructors if and as necessary, as you did in B's constructor.

Update: Sorry for missing your main point! Thanks to ildjarn.

However, your B inherits virtually from A. According to the standard (10.1.4 in the FIDS), "for each distinct baseclass that is specified virtual, the most derived object shall contain a single base class subobject of that type". In your case this means that when constructing the base, your class F immediately calls A's default constructor, not B's.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    How does this answer his question? `F`'s default constructor implcitly invokes `B`'s default constructor, which initializes `A::s` with `"B"`, but `f.s` is empty. I.e., his question is why does [this](http://ideone.com/3xQf4) print an empty string? – ildjarn Jun 23 '11 at 23:36
  • @ildjarn: You're right, sorry! It all hinges on `B` inheriting virtually from `A`. I will amend that! – Kerrek SB Jun 23 '11 at 23:47
  • 1
    @Ben: By default constructor. You could say `F::F() : B(...), A(...) { }`. See the comments in DeadMG's question + source examples. The default constructor (or whichever you specify) of `B` does get called, just not any calls to `A`'s constructor from within `B`'s constructor, since, as DeadMG nailed it, "the virtual baseclass is constructed by the most derived class", i.e. `F`. – Kerrek SB Jun 24 '11 at 00:00
  • @Kerrek : Technically, the virtual base subobject is initialized before the direct base subobject, so `F::F() : A(...), B(...) { }` would be better. I think GCC even gives a warning for `F::F() : B(...), A(...) { }`. – ildjarn Jun 24 '11 at 00:16
7

Virtual base classes are always constructed by the most derived class.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • And doesn't explain the exhibited behaviour at all. – Lightness Races in Orbit Jun 23 '11 at 23:49
  • 4
    @Tomalak : It does explain the exhibited behavior, because `C`, `D`, `E`, and `F` are responsible for initializing their `A` subobjects, and none do; consequently, their `A` subobjects are always default constructed, despite the red herring of the latter 3 inheriting from `B`. – ildjarn Jun 23 '11 at 23:53
  • @ildjarn: Oh, I get you. OK. Is `F` able to provide that parameter, then? – Lightness Races in Orbit Jun 23 '11 at 23:56
  • Very nice. Sorry for making a mess of this, this is a much better answer. Can you clarify: When `F` is instantiated, `B`'s default constructor does get called (to initialize the `B`'s objects), but with the exception of the call to `A(string)`? – Kerrek SB Jun 23 '11 at 23:59
  • 4
    @Tomalak : Yep, it's _not_ intuitive. :-] The semantics of virtual inheritance is one of the prime reasons multiple inheritance is not allowed in e.g. Java and C#. – ildjarn Jun 24 '11 at 00:00
  • 1
    @Kerrek : `B`'s constructor runs, it simply doesn't initialize its `A` subobject since in this context it is not the most derived type. See [here](http://ideone.com/g4Chk). – ildjarn Jun 24 '11 at 00:04
  • @ildjarn: Yes, I saw that the `A` subobject doesn't get initialized, but I was just wondering if that means effectively that all calls to `A(...)` in any of `B`'s constructors' initializer lists are ignored in constructors of descendants of `B`. – Kerrek SB Jun 24 '11 at 00:18
  • @ildjarn: Perhaps this is the crux of the peculiarity of virtual inheritance: Syntactically it looks like you're calling a function which is in turn calling another function, but that's not what's happening. A veritable gotcha! :-) – Kerrek SB Jun 24 '11 at 00:21
  • @Kerrek : I know you're clear on the situation now, but it occurs to me now (20 hours later :-P) that [this](http://ideone.com/L9l6A) would have been a better demonstration. – ildjarn Jun 24 '11 at 20:53