Delegation through pure virtual


A

aksinghdce

Hi,

Please consider this example from C++ FAQ {
http://www.parashift.com/c++-faq-lite
}

class Base {
public:
virtual void foo() = 0;
virtual void bar() = 0;
};

class Der1 : public virtual Base {
public:
virtual void foo();
};

void Der1::foo()
{ bar(); }

class Der2 : public virtual Base {
public:
virtual void bar();
};

class Join : public Der1, public Der2 {
public:
...
};

int main()
{
Join* p1 = new Join();
Der1* p2 = p1;
Base* p3 = p1;

p1->foo();
p2->foo();
p3->foo();
}

Could you please explain how the compiler ( in general ) would create
virtual tables for Der1, Der2 and Join classed. I really need to
understand how this->bar() in function Der1::foo() gets translated to
a call to Der2::bar() call.
As per the explanation in 20.4, the Der1's vtable should have a
pointer &Base::bar() because bar() is not implemented in Der1; by what
mechanism this pointer points to &Der2:bar()?
I realize this would be a very naive question, but I need this to be
answered by experts like you.
 
Ad

Advertisements

J

James Kanze

Please consider this example from C++ FAQ
{http://www.parashift.com/c++-faq-lite}

class Base {
public:
virtual void foo() = 0;
virtual void bar() = 0;
};
class Der1 : public virtual Base {
public:
virtual void foo();
};
void Der1::foo()
{ bar(); }
class Der2 : public virtual Base {
public:
virtual void bar();
};
class Join : public Der1, public Der2 {
public:
...
};
int main()
{
Join* p1 = new Join();
Der1* p2 = p1;
Base* p3 = p1;

Could you please explain how the compiler ( in general ) would
create virtual tables for Der1, Der2 and Join classed.

The same way it creates any virtual table. It determs,
according to the rules of the language, which function should be
called, and puts the address of that function in the vtable.
(Often, it also puts additional information, such as any
corrections needed for the this pointer, in the vtable.)
I really need to understand how this->bar() in function
Der1::foo() gets translated to a call to Der2::bar() call.

The same way any virtual function call is translated.
As per the explanation in 20.4, the Der1's vtable should have a
pointer &Base::bar() because bar() is not implemented in Der1; by what
mechanism this pointer points to &Der2:bar()?

The vptr always points to a vtable corresponding to the most
derived class (once constructors have finished, and before
destructors have started). The vptr in Der1 will point to a
vtable_Der1InJoin. (The compiler may optimize this, if, for
example, vtable_Der1InJoin is identical to the start of
vtable_Join.)
 
A

aksinghdce

The same way it creates any virtual table.  It determs,
according to the rules of the language, which function should be
called, and puts the address of that function in the vtable.
(Often, it also puts additional information, such as any
corrections needed for the this pointer, in the vtable.)


The same way any virtual function call is translated.




The vptr always points to a vtable corresponding to the most
derived class (once constructors have finished, and before
destructors have started).  The vptr in Der1 will point to a
vtable_Der1InJoin.  (The compiler may optimize this, if, for
example, vtable_Der1InJoin is identical to the start of
vtable_Join.)

James, thanks for such a nice explanation. But, what I am missing here
is that, in Der1's v-table, pointers to virtual functions will be
pointing to the functions in the base class with the exception of the
functions, which have got their definition in Der1.
Now, the scenario is:

Der1's Vtable
pointer1-->Der1::foo(), pointer2-->Base::bar(), .....and other
pointers

As the definition of bar() is not there in Der1, its v-tables's
pointer should point to the function in Base()
My question is, how is it getting pointed to Der2::bar()???

Please give a descriptive explanation.

Thanks,
Amit
 
Ö

Öö Tiib

James, thanks for such a nice explanation. But, what I am missing here
is that, in Der1's v-table, pointers to virtual functions will be
pointing to the functions in the base class with the exception of the
functions, which have got their definition in Der1.
Now, the scenario is:

Der1's Vtable
pointer1-->Der1::foo(), pointer2-->Base::bar(), .....and other
pointers

As the definition of bar() is not there in Der1, its v-tables's
pointer should point to the function in Base()
My question is, how is it getting pointed to Der2::bar()???

Please give a descriptive explanation.

Der1 is abstract. You can not create objects of classes derived from
Der1 without overriding bar().

Base is derived from virtually. It is same virtual subobject for both
Der1 and Der2 in Join.

As result Base subobject has both virtual functions set and so Join is
not abstract. You should not have abstract objects run time.
 
J

James Kanze

James, thanks for such a nice explanation. But, what I am missing here
is that, in Der1's v-table, pointers to virtual functions will be
pointing to the functions in the base class with the exception of the
functions, which have got their definition in Der1.

You seem to have missed an important point in my comments. Der1
doesn't have a "vtable", it has many "vtables", one for each
most derived class which contains it. When you construct a
Join, the constructor of Join sets up Der1 and Der2's vptr to
point to vtable_Der1InJoin and vtable_Der2InJoin. (In most
implementations, given the hierarchy you've defined,
vtable_Der1InJoin will be a prefix of vtable_Join, and in
Join, Der1 and Join will share the same vptr. This will not be
the case for Der2, however.)

This is actually a bit of a simplification: during construction,
the dynamic type of the object is the type being constructed, so
that during the constructor of Der1, the vptr of Der1 will point
to vtable_Der1, and a call to bar will result in undefined
behavior. But once construction has finished, the vptr's will
all point to the correct vtable.
Now, the scenario is:
Der1's Vtable
pointer1-->Der1::foo(), pointer2-->Base::bar(), .....and other
pointers

That's the vtable of Der1 as most derived class. (Since Der1 is
abstract, it will only be used in the constructor.)
As the definition of bar() is not there in Der1, its v-tables's
pointer should point to the function in Base()
My question is, how is it getting pointed to Der2::bar()???

The constructor of Join set the vptr of Der1 and Der2 to point
to vtable which are valid for Der1 and Der2 which are members of
Join.
 
A

aksinghdce

You seem to have missed an important point in my comments.  Der1
doesn't have a "vtable", it has many "vtables", one for each
most derived class which contains it.  When you construct a
Join, the constructor of Join sets up Der1 and Der2's vptr to
point to vtable_Der1InJoin and vtable_Der2InJoin.  (In most
implementations, given the hierarchy you've defined,
vtable_Der1InJoin will be a prefix of vtable_Join, and in
Join, Der1 and Join will share the same vptr.  This will not be
the case for Der2, however.)

This is actually a bit of a simplification: during construction,
the dynamic type of the object is the type being constructed, so
that during the constructor of Der1, the vptr of Der1 will point
to vtable_Der1, and a call to bar will result in undefined
behavior.  But once construction has finished, the vptr's will
all point to the correct vtable.


That's the vtable of Der1 as most derived class.  (Since Der1 is
abstract, it will only be used in the constructor.)


The constructor of Join set the vptr of Der1 and Der2 to point
to vtable which are valid for Der1 and Der2 which are members of
Join.

Thats a real nice explanation. I would like to learn more about this;
please do me a favor and suggest me a book and/or an article or such
so as to let me dive deeper. Thanks a ton :)
 
Ad

Advertisements

J

James Kanze

[...]
Thats a real nice explanation. I would like to learn more about this;
please do me a favor and suggest me a book and/or an article or such
so as to let me dive deeper. Thanks a ton :)

I picked it up from the ARM, which is for the most part fairly
dated (although its explination of how one might implement
multiple inheritance is still valid); it's also fairly succinct.
I seem to recall that Lippman had a book, "Inside the C++ Object
Model", or something like that; it's likely to be dated as well,
but again, how multiple inheritance is handled hasn't changed
much. (Or has it? I believe that a lot of modern compilers use
trampolines, which wasn't the case back when he wrote the book.)
 
Q

Qi

Thats a real nice explanation. I would like to learn more about this;
please do me a favor and suggest me a book and/or an article or such
so as to let me dive deeper. Thanks a ton :)

"Inside the C++ object model" maybe a good book for you if you
want to learn more.

I had read it, and would recommend it if you want to learn the "inside"
of C++ object model.
 
A

aksinghdce

"Inside the C++ object model" maybe a good book for you if you
want to learn more.

I had read it, and would recommend it if you want to learn the "inside"
of C++ object model.

Thanks you very much James. Thanks WQ :)
 
J

Juha Nieminen

aksinghdce said:
Thats a real nice explanation. I would like to learn more about this;
please do me a favor and suggest me a book and/or an article or such
so as to let me dive deeper. Thanks a ton :)

Perhaps the confusion stems from not realizing that objects
instantiated from classes with virtual functions have a vtable
pointer inside them (in practice almost always at the very beginning
of the memory block occupied by the object). This means that the
the instantiations (ie objects) of the same class can actually
point to *different* vtables. (The structure of these vtables is
obviously always the same, but their content can differ, ie. the
function pointers inside them can point to different functions.)

How virtual functions are handled (even in diamond inheritance
situations) is rather simple, relatively speaking. However, it
becomes interesting how access to base class member variables is
handled in a diamond inheritance situation with virtual inheritance.
In a non-diamond-inheritance (or a non-virtual inheritance) situation
accessing member variables of the base class is trivial, but in
a virtual diamond inheritance situation it's a bit more complicated.

Another interesting topic is how dynamic_cast works in complex
multiple inheritance situations. For example, assume this situation:

class A { public: virtual ~A(); ... };
class B { public: virtual ~B(); ... };

void foo(A* a)
{
B* b = static_cast<B*>(a); // Will not compile
B* b = dynamic_cast<B*>(b); // Will compile
}

The classes A and B are not related to each other in any way, and
hence the static_cast will obviously fail to compile because the
types are incompatible. The dynamic_cast will, however, compile.
If the object given to foo() is not related to B in any way, the
dynamic_cast will simply return a null pointer.

Can the dynamic_cast give something else than null in some
situation? The answer is yes. For example if called like this:

class C: public A, public B { ... };

C c;
foo(&c);

Now the dynamic_cast will actually detect that the object is
actually also of type B, and it will change the pointer to point
to the B part of the object.

*How* it does that is the interesting question.
 
Ad

Advertisements

L

ld

Thanks you very much James. Thanks WQ :)

You can have a look to something I did a some time ago (around 2003)

http://cern.ch/laurent.deniau/html/cpp_object_model.tgz

Here is the abstract of the package:

"C++ Object Model" is a long paper (see object_model.html) started
years ago which I unfortunately never finished (about 25% achieved),
but is enough to understand the overall. It comes with a complete
example in C (see cmd_c.sh) and C++ (see cmd_cpp.sh) which describes
one way to implement the C++ object model (i.e. the g++'s object
model). It is addressed to advanced (and motivated ;-) C and C++
programmers who have some interest in the C++ object model, so I put
it here for curiosity. It may give you some hints about the
implementation complexity corresponding to the management of dynamic
types within constructors and destructors (i.e. intermediate vtbl) and
covariant and contravariant types in the presence of multiple and
virtual inheritance (i.e. thunks). I found interesting that my naive
implementation of dynamic_cast is about x3 faster than the
dynamic_cast provided by g++ ;-)

Hope this helps.

Laurent.
 
J

James Kanze

Perhaps the confusion stems from not realizing that objects
instantiated from classes with virtual functions have a vtable
pointer inside them (in practice almost always at the very
beginning of the memory block occupied by the object). This
means that the the instantiations (ie objects) of the same
class can actually point to *different* vtables. (The
structure of these vtables is obviously always the same, but
their content can differ, ie. the function pointers inside
them can point to different functions.)
How virtual functions are handled (even in diamond inheritance
situations) is rather simple, relatively speaking.

Finding the actual address of the function to be called is
rather simple. Finding the value to pass as the this pointer
can be somewhat more complicated, at least when virtual
inheritance is involved (since the offset with respect to the
base pointer is no longer a constant).
 
A

aksinghdce

Finding the actual address of the function to be called is
rather simple.  Finding the value to pass as the this pointer
can be somewhat more complicated, at least when virtual
inheritance is involved (since the offset with respect to the
base pointer is no longer a constant).

Tried to correlate these concepts with GDB. Created a document. This
again lead me to a confusion zone.
There are two cases in case of virtual inheritance:
1. The tip-of-the-diamond class has data members
2. The tip-of-the-diamond class has no data members

In the 2nd case I don't see any v-pointer corresponding to Der1 and
Der2. In place of Der1 v-pointer there is an invalid address. Should I
conclude that the _vptr$Base points to the v-table containing the
methods of all the intermediate derived classes, for instance, Der1
and Der2 in our example code?
Which v-table points to the function defined only in the Join class,
for instance, Join::junk()?

<iframe src="https://docs.google.com/document/pub?id=1Ax7CwM8F-
xSnyPmlP3Gmq0zr0FhJlDTn6Zij6VMl3Tk&amp;embedded=true"></iframe>

This is the link containing all the GDB outputs and the two cases code
and output.
https://docs.google.com/document/pub?id=1Ax7CwM8F-xSnyPmlP3Gmq0zr0FhJlDTn6Zij6VMl3Tk
 
J

James Kanze

Tried to correlate these concepts with GDB. Created a document. This
again lead me to a confusion zone.
There are two cases in case of virtual inheritance:
1. The tip-of-the-diamond class has data members
2. The tip-of-the-diamond class has no data members

At least in the past, most compilers didn't make this
distinction.
In the 2nd case I don't see any v-pointer corresponding to Der1 and
Der2. In place of Der1 v-pointer there is an invalid address. Should I
conclude that the _vptr$Base points to the v-table containing the
methods of all the intermediate derived classes, for instance, Der1
and Der2 in our example code?

In your example, the compiler has a lot of options with regards
to optimizing the use of vtables (and vptr). Adding data would
eliminate some of them. (With no data, I think that the
compiler could use empty base class optimization to put the
start of all four classes at the same address. With data,
practically speaking, only Der1 and Join could share an
address.)

The traditional layout would be something like:

+-----------------+
| vptr, Der1+Join | <--- address Der1, Join
+-----------------+
| Der1 data |
+-----------------+
| vptr, Der2 | <--- address Der2
+-----------------+
| Der2 data |
+-----------------+
| Join data |
+-----------------+
| vptr, Base | <--- address Base
+-----------------+
| Base data |
+-----------------+

The respective vtable would contain the addresses of the various
virtual functions, but also information for RTTI, the position
of Base relative to the position of the vptr, and for each
function, how to obtain a correct this pointer. Thus, if we
assume that the vptr has a size of 4, and that each class has 4
bytes of class specific data, and that there are no additional
alignment requirements (in other words, that each rectangle
above represents 4 byts), the vtable pointed to by the first
vptr might look like:

__vtable_Join:
&__rttiInfo_Join,
20,
&Der1::foo
0
&Der2::bar
8
&Join::junk
0

The second would be:

__vtable_Der2__in__Join:
&__rttiInfo_Join,
12,
&Der1::foo
-8
&Der2::bar
0

And the last:

__vtable_Base__in__Join:
&__rttiInfo_Join,
0,
&Der1::foo
-20
&Der2::bar
-12

This layout can handle all hierarchies (although it gets more
complicated when there are several virtual bases---quite likely,
the offset to find the virtual bases will be handled
differently, to provide for this: IIRC, CFront used a separate
pointer, in each class, for each virtual base). Given that
neither Der1 nor Der2 introduce any additional virtual
functions, if they had no data, it would be possible to merge
them, and only use a single vtable for Join for both of them.
It might even be possible to merge Base with both of them, and
use just a single vtable.
Which v-table points to the function defined only in the Join class,
for instance, Join::junk()?

None. As I've already said several times, at least one base
class will typically share the vtable with the derived class.
 
Ad

Advertisements

A

aksinghdce

At least in the past, most compilers didn't make this
distinction.


In your example, the compiler has a lot of options with regards
to optimizing the use of vtables (and vptr).  Adding data would
eliminate some of them.  (With no data, I think that the
compiler could use empty base class optimization to put the
start of all four classes at the same address.  With data,
practically speaking, only Der1 and Join could share an
address.)

The traditional layout would be something like:

    +-----------------+
    | vptr, Der1+Join | <--- address Der1, Join
    +-----------------+
    |    Der1 data    |
    +-----------------+
    |   vptr, Der2    | <--- address Der2
    +-----------------+
    |    Der2 data    |
    +-----------------+
    |    Join data    |
    +-----------------+
    |   vptr, Base    | <--- address Base
    +-----------------+
    |    Base data    |
    +-----------------+

The respective vtable would contain the addresses of the various
virtual functions, but also information for RTTI, the position
of Base relative to the position of the vptr, and for each
function, how to obtain a correct this pointer.  Thus, if we
assume that the vptr has a size of 4, and that each class has 4
bytes of class specific data, and that there are no additional
alignment requirements (in other words, that each rectangle
above represents 4 byts), the vtable pointed to by the first
vptr might look like:

    __vtable_Join:
        &__rttiInfo_Join,
        20,
        &Der1::foo
        0
        &Der2::bar
        8
        &Join::junk
        0

The second would be:

    __vtable_Der2__in__Join:
        &__rttiInfo_Join,
        12,
        &Der1::foo
        -8
        &Der2::bar
        0

And the last:

    __vtable_Base__in__Join:
        &__rttiInfo_Join,
        0,
        &Der1::foo
        -20
        &Der2::bar
        -12

This layout can handle all hierarchies (although it gets more
complicated when there are several virtual bases---quite likely,
the offset to find the virtual bases will be handled
differently, to provide for this: IIRC, CFront used a separate
pointer, in each class, for each virtual base).  Given that
neither Der1 nor Der2 introduce any additional virtual
functions, if they had no data, it would be possible to merge
them, and only use a single vtable for Join for both of them.
It might even be possible to merge Base with both of them, and
use just a single vtable.


None.  As I've already said several times, at least one base
class will typically share the vtable with the derived class.

Thanks you, James!
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top