I found this in a website while reading about virtual inheritance in c++
When multiple inheritance is used, it is sometimes necessary to use virtual inheritance. A good example for this is the standard iostream class hierarchy:
//Note: this is a simplified description of iostream classes
class ostream: virtual public ios { /*..*/ }
class istream: virtual public ios { /*..*/ }
class iostream : public istream, public ostream { /*..*/ }
//a single ios inherited
How does C++ ensure that only a single instance of a virtual member exists, regardless of the number of classes derived from it? C++ uses an additional level of indirection to access a virtual class, usually by means of a pointer. In other words, each object in the iostream hierarchy has a pointer to the shared instance of the ios object. The additional level of indirection has a slight performance overhead, but it's a small price to pay.
i am confused with the statement:
C++ uses an additional level of indirection to access a virtual class, usually by means of a pointer
could anybody explain this?
The basic problem to solve is that if you cast a pointer to the most derived type to a pointer to one of its bases, the pointer must refer to an address in memory from which each member of the type can be located by code that does not know of derived types. With non-virtual inheritance, this is usually achieved by having the exact layout, and that in turn is achieved by containing a base class subobject and then adding the extra bits of the derived type:
struct base { int x; };
struct derived : base { int y };
Layout for derived:
--------- <- base & derived start here
x
---------
y
---------
If you add a second derived and a most derived types (again, without virtual inheritance) you get something like:
struct derived2 : base { int z; };
struct most_derived : derived, derived 2 {};
With this layout:
--------- <- derived::base, derived and most_derived start here
x
---------
y
--------- <- derived2::base & derived2 start here
x
---------
z
---------
If you have a most_derived
object and you bind a pointer/reference of type derived2
it will point to the line marked with derived2::base
. Now, if inheritance from base was virtual, then there should be a single instance of base
. For the sake of discussion, just assume that we naïvely remove the second base
:
--------- <- derived::base, derived and most_derived start here
x
---------
y
--------- <- derived2 start here??
z
---------
Now the problem is that if we obtain a pointer to derived
it has the same layout as the original, but if we tried to obtain a pointer to derived2
the layout would differ and code in derived2
would not be able to locate the x
member. We need to do something smarter, and that is where the pointer comes into play. By adding a pointer to each object that inherits virtually, we get this layout:
--------- <- derived starts here
base::ptr --\
y | pointer to where the base object resides
--------- <-/
x
---------
Similarly for derived2
. Now, at the cost of the extra indirection we can locate the x
subobject through the pointer. When we can create most_derived
layout with a single base, it could look like this:
--------- <- derived starts here
base::ptr -----\
y |
--------- | <- derived2
base::ptr --\ |
z | |
--------- <--+-/ <- base
x
---------
Now code in derived
and derived2
nows how to access the base subobject (just dereference the base::ptr
member object), and at the same time you have a single instance of base
. If code in either intermediate class access x
they can do so by doing this->[hidden base pointer]->x
, and that will be resolved at runtime to the proper position.
The important bit here is that code compiled at the derived
/derived2
layer can be used with an object of that type, or any derived object. If we wrote a second most_derived2
object where the order of inheritance was reversed, then they layout of y
and z
could be swapped, and the offsets from a pointer to the derived
or derived2
subobjects to the base
subobject will be different, but the code to access x
would still be the same: dereference your own hidden base pointer, guaranteeing that if a method in derived
is the final overrider, and that access base::x
then it will find it regardless of the final layout.
Basically, if virtual inheritance is not used, base members are actually part of derived class instances. Memory for the base members is allocated in each instance, and no further indirection is necessary to access them:
class Base {
public:
int base_member;
};
class Derived: public Base {
public:
int derived_member;
};
Derived *d = new Derived();
int foo = d->derived_member; // Only one indirection necessary.
int bar = d->base_member; // Same here.
delete d;
However, when virtual inheritance comes into play, virtual base members are shared by all classes in their inheritance tree, instead of several copies being created when the base class is multiply inherited. In your example, iostream
only contains one shared copy of the ios
members, even though it inherits them twice from both istream
and ostream
.
class Base {
public:
// Shared by Derived from Intermediate1 and Intermediate2.
int base_member;
};
class Intermediate1 : virtual public Base {
};
class Intermediate2 : virtual public Base {
};
class Derived: public Intermediate1, public Intermediate2 {
public:
int derived_member;
};
That means an extra indirection step is required in order to access virtual base members:
Derived *d = new Derived();
int foo = d->derived_member; // Only one indirection necessary.
int bar = d->base_member; // Roughly equivalent to
// d->shared_Base->base_member.
delete d;
In C++ a class is laid out in memory in a fixed order. A base class exists literally inside the memory allocated to the derived class, at a fixed offset, analogous to a smaller box inside a larger box.
If you don't have virtual inheritance you are saying iostream
contains an istream
and an ostream
each of which contains an ios
. Therefore an iostream
contains two ios
es.
With virtual inheritance, the virtual base class doesn't exist at a fixed offset. It is analogous to hanging onto the outside of the box, connected with a bit of string.
So then iostream
contains an istream
and an ostream
each of which is linked to an ios
by string. Therefore an iostream
has one ios
, linked by two separate bits of string.
In practice the bit of string is an integer which says where the actual ios
starts relative to the address of the derived class. I.e. the istream
has a hidden member called, for example, __virtual_base_offset_ios
. When the istream
methods want to access the ios
base, they take their own this
pointer, add __ios_base_offset
and that is the ios
base class pointer.
-
In other words, In non-virtually derived classes, the derived classes know what the offset to the base class is because it is fixed, and physically inside the derived class. In virtually derived classes, the base class has to be shared, so it cannot always exist inside the derived class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With