Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual inheritance in C++

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?

like image 617
Vijay Avatar asked Mar 03 '11 11:03

Vijay


3 Answers

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.

like image 63
David Rodríguez - dribeas Avatar answered Oct 12 '22 01:10

David Rodríguez - dribeas


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;
like image 36
Frédéric Hamidi Avatar answered Oct 12 '22 03:10

Frédéric Hamidi


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 ioses.

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.

like image 45
Ben Avatar answered Oct 12 '22 02:10

Ben