Michael said:
Dhruv wrote:
Hi,
It seems I can influence how a base class is initialized beyond the
'normal' manner and I was wondering if someone can tell me why this.
Here's my example.
[snip]......
For virtual bases, the rule is different. The most derived class is
responsible for constructing the virtual base class, as opposed to its
immediate descendant in case of non-virtual bases.
But wait a minute, if I remove the explicit initialization of A in C's
constructor list, A will get initialized by B.
Nope. If you remove the explicit initialization of A in C's initializer
list and then invoke the class C ctor,
C(int a=1, int b=1, int c=1)
: B(a,b,c)
{ }
the sequence of events (IIRC) is this:
1) Program invokes the C ctor
2) The C ctor implicitly invokes A's default ctor
3) The C ctor invokes 'B(a,b,c)' as specified in the initializer list of
the class C ctor. The B ctor does not invoke the A ctor.
So the class C ctor -- not the B ctor -- is still the entity that
invokes the class A ctor.
FWIW, if you rewrite your code a bit, this sequence of events will (IMO)
be easier to observe.
<example>
<code>
#include <iostream>
using namespace std;
class A {
public:
A(int a=1, int b=1, int c=1)
: a_(a), b_(b), c_(c)
{ cout << "A(" << a << ',' << b << ',' << c << ")\n"; }
void print()
{ cout << a_ << ',' << b_ << ',' << c_ << '\n'; }
private:
int a_, b_, c_;
};
class B : public virtual A {
public:
B(int a=2, int b=2, int c=2)
: A(a, b, c)
{ cout << "B(" << a << ',' << b << ',' << c << ")\n"; }
};
class C : public virtual B {
public:
C(int a=3, int b=3, int c=3)
// : A(4,4,4), B(a,b,c)
: B(a,b,c)
{ }
};
int main()
{
C c;
c.print();
}
</code>
<output>
A(1,1,1)
B(3,3,3)
1,1,1
</output>
</example>
Some side notes:
1) DO NOT use variable names that start with a leading underscore, e.g.,
'_a'. These names (and some others) are reserved for the
implementation's use -- e.g., the standard library, compiler-specific
language extensions, the OS's API, etc. So if you want to use
underscores to denote variable names, place the underscore at the end of
the name, 'a_', and not at the beginning.
2) In this code sample, class A's ctor is *always* invoked before class
B's ctor. This sequence holds even if you try (futilely) to specify the
base class ctor invocation sequence as B first, then A, in the class C
ctor initializer list:
C() : B(), A() { }
IOW, the compiler ignores the B,A sequence specified above, and
implicitly implements C's initializer list as A,B, i.e.,
C() : A(), B() { }
[n.b. Most compilers are nice enough to warn you when they implicitly
reorder the initializer list's arguments like this.]