views:

141

answers:

4

Hi there:

I encounter this problem when tackling with virtual inheritance. I remember that in a non-virtual inheritance hierarchy, object of sub-class hold an object of its direct super-class. What about virtual inheritance? In this situation, does object of sub-class hold an object of its super-class directly or just hold a pointer pointing to an object of its super-class?

By the way, why the output of the following code is:

sizeof(A): 8
sizeof(B): 20
sizeof(C): 20
sizeof(D): 36

Code:

#include <iostream>

using namespace std;

class A{
    char k[ 3 ];
    public:
        virtual void a(){};
};

class B : public virtual A{
    char j[ 3 ];
    public:
        virtual  void b(){};
};

class C : public virtual A{
    char i[ 3 ];
    public:
        virtual void c(){};
};

class D : public B, public C{
    char h[ 3 ];
    public:
        virtual void d(){};
};

int main( int argc, char *argv[] ){
    cout << "sizeof(A): " << sizeof( A ) << endl;
    cout << "sizeof(B): " << sizeof( B ) << endl;
    cout << "sizeof(C): " << sizeof( C ) << endl;
    cout << "sizeof(D): " << sizeof( D ) << endl;

    return 0;
}

Thanks in advance. Kind regards.

+1  A: 

I remember that in a non-virtual inheritance hierarchy, object of sub-class hold an object of its direct super-class.
That is not correct. Several implementations are going to do it this way, but it's not defined that way by Standard C++. Standard C++ does not specify how any of these things are to be implemented.

Virtual Inheritance is used only for some cases of multiple inheritance where a derived class multiply inherits from two base classes which themselves inherit from a common base class. An example of this is the iostream library, where istream and ostream inherit from basic_ios, and iostream inherits from istream and ostream (and so one iostream would have two basic_ios without virtual inheritance).

Unless you are in this specific scenario, you should not use virtual inheritance.

What about virtual inheritance? In this situation, does object of sub-class hold an object of its super-class directly or just hold a pointer pointing to an object of its super-class?
That is implementation defined. You do not need to know nor should you ever make any assumptions about this. Suffice to say that there is a runtime penalty for virtual inheritance, which is why you should avoid it when it is not needed.

Billy ONeal
+5  A: 

The virtual base object is somewhere in the memory block that belongs to the object (the memory with size = sizeof(object)). Because several sub objects of different types can be combined in various ways but must share the same base object, a offset pointer is needed for each sub object to find out the virtual base object. Without virtual inheritance, the offset to find out the corresponding base object is fixed at compile time for each class type.

The sizeof values depend on your compiler and machine, but the following assumptions are very common:

assumption: pointer size is 4 bytes

assumption: class size is rounded up to multiple of 4 bytes

sizeof(A): 8  ->   1 pointer to vtable (virtual method) 
                 + 3 chars -> 4+3=7 
              -> round up to 8

sizeof(B): 20 ->   8 + 1 pointer to vtable (virtual method) 
                 + 1 offset pointer to virtual base 
                 + 3 chars -> 8 + 4 + 4 + 3 = 19 
              -> round up to 20

sizeof(C): 32 ->  20 + 1 pointer to vtable (virtual method) 
                 + 1 offset pointer to virtual base 
                 + 3 chars 
              -> 20 + 4 + 4 + 3 = 31 // this calculation refers to an older 
              -> round up to 32      // version of the question's example 
                                     // where C had B as base class

The calculations are guessed because the real calculation must exactly know how the compiler works.

Regards, Oliver

More details why an extra offset pointer is needed:

Example:

class B  : virtual public A {...};
class C  : virtual public A {...};
class D1 : public B {...};
class D2 : public B, C {...};

possible memory layout for D1:

A
B
D1

possible memory layout for D2:

A
C
B
D2

in the second case sub object B needs another offset to find its base A

An object of type D2 consists of a memory block, where all the parent object parts are contained, i.e. the memory block for an object of type D2 has a section for the A member variables, the C member variables, the B member variables and the D2 member variables. The order of these sections is compiler dependent, but the example shows, that for multiple virtual inheritance a offset pointer is needed, that points within the object's total memory block to the virtual base object. This is needed because the methods of class B know only one this pointer to B and must somehow calculate where the A memory part is relative to the this pointer.

Calculation sizeof(D):

sizeof(D): 36 ->   A:3 chars + A:vtable 
                 + B:3 chars + B:vtable + B:virtual base pointer
                 + C:3 chars + C:vtable + C:virtual base pointer
                 + D:3 chars + D:vtable
               =   3 + 4 
                 + 3 + 4 + 4 
                 + 3 + 4 + 4 
                 + 3 + 4 
                 = 36

The above calculation is probably wrong ;-) ...

I'm not sure whether the D part has its own vtable pointer or not (this is all highly compiler dependent).

I now think that it could be that the D part use the vtable pointer entry of its parent classes and that the 4 extra bytes are used for alignment each part (multiple of 8 bytes):

So this calculation is probably more correct:

sizeof(D): 36 ->   A:3 chars + A:vtable + A:alignment
                 + B:3 chars + B:vtable + B:virtual base pointer + B:alignment
                 + C:3 chars + C:vtable + C:virtual base pointer + C:alignment
                 + D:3 chars + D:alignment
               =   3 + 4 + 1
                 + 3 + 4 + 4 + 1 
                 + 3 + 4 + 4 + 1
                 + 3 + 1
                 = 36
Oliver
@Oliver: Thanx for your reply, but since object of class B holds an object of class A, why more 4 bytes are needed to record the offset to the virtual base? I'm not very clear about that.
Summer_More_More_Tea
-1: This may be true about one particular implementation, but it's undefined according to the standard. @Summer_More_More_Tea: Because virtual inheritance means only once instance of the common base class exists. This is needed to solve some problems with multiple inheritance.
Billy ONeal
@Summer_More_More_Tea: I have edited my answer with more details to answer your question.
Oliver
@Oliver: Thanx again. According to the answer does object B in D2 still has an instance of object A, or just has a pointer to instance of A. And I also have edited my question, why does the output this time is like that, especially sizeof(D) which I originally thought 40.Kind regards.
Summer_More_More_Tea
@Billy: Sorry for last vague question. I have edited my question, how to explain this one? :-). Thanks.
Summer_More_More_Tea
@Summer_More_More_Tea: The inheritance of B and C only results in D having one instance of A. That's the point of virtual inheritance in the first place. Again, look at the standard iostream library. It makes no sense for an instance of `std::iostream` to have two copies of `std::ios_base`, despite the fact that it inherits from both `std::istream` and `std::ostream`, both of which inherit from `std::ios_base`. Therefore virtual inheritance is used and `iostream` has but one copy of `ios_base`. If you want two copies of A in D, don't use virtual inheritance -- it's not necessary then.
Billy ONeal
@Summer_More_More_Tea: If you want a better understanding of how Virtual Inheritance works, I'd have to recommend Scott Meyers' Effective C++ Item #40: *Use multiple inheritance judiciously*, which explains the pros and cons of virtual inheritance, as well as the point of VI.
Billy ONeal
@Summer_More_More_Tea: An object of type D2 consists of a memory block, where all the parent object parts are contained, i.e. the memory block for an object of type D2 has a section for the A member variables, the C member variables, the B member variables and the D2 member variables. The order of these sections is compiler dependent, but the example shows, that for multiple virtual inheritance a offset pointer is needed, that points *within* the object's total memory block to the virtual base object. I have edited my answer for calculation the sizeof(D),
Oliver
@Billy: True what you say, but in OP's code, the `virtual`'s are in the wrong places for D to only have one instance of A. See my answer.
Dan
@Dan: you are right: I have not seen that the keyword *virtual* for B is missing in D so the example for D is probably not a good one to investigate the question. So there might be two instances of B in D, bit I think there is only one A. gcc gives warning *warning: direct base `B' inaccessible in `D' due to ambiguity* so my calculation for sizeof(D) is wrong.
Oliver
@Oliver: Thanx a lot.
Summer_More_More_Tea
@Billy: Thanx a lot.
Summer_More_More_Tea
@Billy: never mind, the code had a typo which has since been fixed (C inherits virtually from A now instead of B).
Dan
+1  A: 

I see three point analysis for the above question

a. Virtual Inheritance

"Virtual inheritance is a mechanism whereby a class specifies that it is willing to share the state of its virtual base class. Under virtual inheritance, only one, shared base-class subobject is inherited for a given virtual base regardless of how many times the class occurs as a virtual base within the derivation hierarchy. The shared base-class subobject is called a virtual base class." ... From Lippman

Virtual inheritance only avoids duplicate sub-objects inherited from multiple inheritance. But this does not indicate in any way that the base class objects will not be sub-objects. On the contrary, the sub-object (atleast one copy would be present - I mean would be included in sizeof() operation) even during the virtual inheritance.

b. virtual function

Virtual function is for dynamic binding of member functions of objects involved in hierarchy. So even this does not have any significance towards sub-object arrangements.

c. Implementation of the sub-objects

This is totally compiler dependent, and for all reasons would be very difficult to determine - in its implementation. However, we can confirm that the sizeof() of the object would include the size of the base class (sub) objects also - and we can visualize them as having the base class object embedded in them.

Each object of the inherited function will definitely contain space for the sub-objects.

HTH

kumar_m_kiran
" However, we can confirm that the sizeof() of the object would include the size of the base class (sub) objects also" <-- Actually if an implementation wanted to have the class hold only a pointer to it's base, that would also be permissible.
Billy ONeal
@Neal, I only meant that the sizeof would always provide the sub-object size included. Yes, I agree with you that it can be compiler specific implementation.
kumar_m_kiran
+1  A: 

does object of sub-class hold an object of its super-class directly

Yes, that is how it works whether the inheritance is virtual or not. I would use the word "contain" vs. "hold" however.

If your hierarchy looked like this, with no virtual inheritances anywhere:

#     W    <--- base class
#    / \
#   X   Y  <--- subclasses of W
#    \ /
#     Z    <--- most derived class

Then Z will have two copies of W. But if you make the X-->W and Y-->W inheritances virtual, then Z will only have one copy of W because Z's two superclasses share their common base class.

#     W
#    / \   <--- make these two virtual to eliminate duplicate W in Z.
#   X   Y
#    \ /
#     Z

In your example:

class A{...};
class B : public virtual A{...};
class C : public virtual B{...}; // Edit: OP's code had this typo when I answered
class D : public B, public C{...};

Having B inherit virtually from A isn't necessary. The only virtual inheritances you need are C-->B and D-->B, since that is where the diamond "merges" going up the inheritance hierarchy:

#   What you have     |     What you want?
#             A       |               A
#            /        |              /
#           /v        |             /
#          /          |            /
#         B           |           B
#        / \          |          / \
#       /v  \         |         /v  \v
#      /     \        |        /     \
#     C       )       |       C       )
#      \     /        |        \     /
#       \   /         |         \   /
#        \ /          |          \ /
#         D           |           D

Of course if you have other classes not shown that inherit from A as well as B, that changes things -- maybe the B-->A inheritance does need to be virtual if there is another diamond you didn't tell us about.

Dan
@Dan: A nice answer. However, only one acceptant is permitted and I have annoyed Oliver for about 2 days. :-)
Summer_More_More_Tea
@Dan: Sorry for the typo. My original purpose is class C : virtual public A{}; and I did it on my own PC. The above code yields a compile error... And I have correct my question.
Summer_More_More_Tea
No problem. Actually the code with the typo only gave a compiler warning. The ambiguity only becomes an error if you attempt to access the ambiguous base, for example, like this: `{ D d; B }` I edited my answer to point out it was based on the code with the typo.
Dan