Re: some questions about multiple inheritance
Jess wrote:
On Jun 26, 11:02 pm, "Victor Bazarov" <v.Abaza...@comAcast.net> wrote:
Jess wrote:
First, it is said that if virtual inheritance is used, then "the
responsibility for initializing a virtual base is borne by the most
derived class in the hierarchy". What does it mean?
It means that the compiler generates code such that the virtual base
class' constructor is invoked *not* by the constructor of the class
that directly inherits the virtual base but the most derived one.
Initializing
base class is usually done automatically by the compiler, but a
derived class can invoke the base class' constructor. What special
initialization work has to be done by the most derived class?
Here is an example:
struct vbase {
int i;
vbase(int ii) : i(ii) {}
};
struct a : virtual vbase {
a() : vbase(1) {}
};
struct b : virtual vbase {
b() : vbase(2) {}
};
struct mostderived : a, b {
mostderived() : vbase(42) {} // note 'vbase' initialisation
};
#include <iostream>
int main() {
mostderived m;
std::cout << m.i << std::endl;
}
What do you expect the program to output? The default c-tor of 'a'
makes the 'i' member of 'vbase' 1, 'b' makes it 2. But when the 'm'
object is created in the 'main' function, the 'i' member will be
initialised to 42 because the most derived class is responsible for
initialising the virtual base class.
What
about the intermediate classes (those derived classes that aren't at
the end of the hierarchy)?
What about them? Do you expect to instantiate them independently?
In your example, if "mostderived" wants to init "a" and "b" explicitly
by calling their constructors, I think "mostderived" can do so, right?
If so, would "a" and "b"'s constructors iniit "vbase" by calling
vbase's constructor?
In this case, mostderived will call the constructors for a and
b. An important point to remember: every class type has a
constructor (if you don't provide one, the compiler does), and
that constructor will always be called, any time an instance of
the class exists, be it a sub-object or a complete object.
Given that, the pseudo code for what a compiler generates
automatically at the head of a constructor is more or less:
if ( this_is_most_derived_class ) {
call constructors for all virtual bases
}
call constructors for all direct non-virtual bases
call constructors for all objects
If you provide initializers in the initializer list of the
constructor, the compiler will use these when (and if) it calls
the constructor for the sub-object. The initializer list,
however, does not have any influence on when, or even if, the
constructor is called.
That's the point of Victor's example: the initializers for vbase
in a and in b are not used, since a and b are not the most
derived class. (Note that if the initialization expression has
side effects, these will not occur either.)
Do they need to do anything to init the
virtual base class and does the most derived class need to do anything
to init the intermediate classes?
Yes, in case you _do_ instantiate them.
I think you mean I can call "a" and "b"'s constructors?
I don't think he does. I'm not sure, but I think he means
"initialize", rather than "instantiate". All of the sub-objects
of a type will be instantiated whenever an object of the type is
instantiated, and the constructor will be called for all of the
sub-objects of class type. The only question is when, in what
context and with what initializer list. For non-virtual bases,
the answer is always from the immediately derived class; for
virtual bases, the answer is always from the most derived class.
Second, it is said that we should try to avoid putting data in a
virtual base class.
Is it said, why?
I don't know why, just mentioned in the book.
Part of the reason, doubtlessly, is that data must be
initialized, and that initialization supposes an initial value,
probably provided by an initializer list in a constructor.
Consider once again Victor's example: imagine now that
mostderived needs vbase to be initialized with 42 in order to
work, for some internal reason. Now create an even more derived
class. That new class will have to initialize vbase with 42, or
mostderived will not work. That means that that new class has
to know what should be internal details of the class it is
deriving from.
Even worse, of course, is if a needs vbase to be initialized
with 1 to work, and b needs it to be initialized with 2. I
wouldn't go so far as Scott, and ban all data members, but I
would definitly ensure that any class from which I inherit
virtually has a default constructor; you don't want people
deriving some layers down to have to worry about what
initialization is needed.
In practice, most uses of virtual inheritance are of interfaces,
which naturally don't have any data, nor user defined
constructors, so there's no problem. Often, in mixin's, the
base class has no data either, or if it does, the base class
itself knows how to initialize it, so the default constructor is
all you need as well; if this is not the case, the mixin
hierarchy must be closed, so that there is no risk of someone
providing an incorrect initialization list. If the virtual base
class does require an initializer, and one particular derived
class must provide it, then you have to provide a no-op default
constructor, and an initialize function which will be called by
that class. (This is how iostream handles the initialization of
std::basic_ios.)
[...]
Get a hold of a copy of "Modern C++ Design" by Andrei Alexandrescu.
Multiple inheritance is the cornerstone of the "policy-based design".
Isn't policy-based design just a fancy repackaging of mixins:-)?
(Seriously, I think Andrei also uses templates, so that the
policy decisions are made at compile time, not at runtime. In
which case, you don't really need inheritance at all.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34