covariant return types with multiple inheritance. how does this code work?
Can anyone tell me how does return type covariance work in the following code?
class X
{
public:
int x;
};
class Y: public OtherClass, public X
{
};
static Y inst;
class A {
public:
virtual X* out() = 0;
};
class B : public A
{
public:
virtual Y* out()
{
return &inst;
}
};
void main()
{
B b;
A* a = &b;
//x and y have different addresses. how and when is this conversion done??
开发者_运维百科 Y* y = b.out();
X* x = a->out();
}
EDIT: I'm sorry I must not have been clear enough. x and y point to different addresses just like I expect since there's multiple inheritance involved so X and Y objects are not on the same address. My question is when is this cast being done? the out() function couldn't have done this because it always returns a pointer to Y from its point of view. The caller of out() couldn't have done this because it sees X* whose concrete type might be X or Y. When is the cast done then?
By "how does it work", I presume you're asking about what the generated code looks like. (In a typical implementation, of course. We all know that the generated code can vary somewhat between implementations.) I'm aware of two possible implementations:
The compiler always generates code to return a pointer to the base
class; i.e. in B::out
, the compiler will convert the Y*
to an X*
before returning it. At the call site, if the call was through an
lvalue with static type B
, the compiler will generate code to
reconvert the returned value to Y*
.
Alternatively (and I think this is more frequent, but I'm far from
sure), the compiler generates thunks, so when you call a->out
, the
virtual function which gets called is not directly B::out
, but a small
wrapper which converts the Y*
returned from B::out
to an X*
.
Both g++ and VC++ seem to use thunks (from a very quick glance).
When is the cast done then?
Well, it is done after B::out
returns, and before the A::out
call ends. There really isn't much to it.
The pointer returned by the polymorphic call must be of type X*
. This means it has to point to an object of type X
. Since you're using multiple inheritance, your Y
objects can have several subobjects in them. In your case they can have an OtherClass
subobject and an X
subobject. Obviously, these subobjects are not stored in the same address. When you ask for a pointer to X*
you get a pointer to the X
subobject and when you ask for a pointer to Y*
you get a pointer to the whole Y
object.
class OtherClass { int a; };
// the other classes
int main()
{
B b;
A* a = &b;
//x and y have different addresses. how and when is this conversion done??
Y* y = b.out();
X* x = a->out();
OtherClass* o = y;
std::cout << "x: " << x << std::endl;
std::cout << "y: " << y << std::endl;
std::cout << "o: " << o << std::endl;
}
You can see that running this code yields different addresses for the X*
and Y*
pointers and the same address for the Y*
and OtherClass*
pointers, because the object was laid out with the OtherClass
subobject before the X
subobject.
x: 0x804a15c
y: 0x804a158
o: 0x804a158
NOTE: first answer is only correct for single inheritance, multiple inheritance below (edit2).
it's normal for x and y to have different addresses because it are two different pointers. They do have the same value though, which is the adress of the variable they're pointing to.
edit: you can use this main to check what I mean, the first line will print the value of x
and y
(i.e. the address they're pointing to) which always should be the same because actually the same method will be called due to out
being virtual. The second will print their own address, which are of course different because they're different (pointer) variables.
#include <iostream>
int main()
{
B b;
A* a = &b;
//x and y have different addresses. how and when is this conversion done??
Y* y = b.out();
X* x = a->out();
std::cout << y << std::endl << x << std::endl;
std::cout << &y << std::endl << &x << std::endl;
return 0;
}
edit2: okay, this was wrong, my apologies. When multiple inheritance comes into play the implicit cast from Y*
to X*
(so at the assignment of x, not the return of out
) will change the pointer address.
This happens because at implementation, the layout of Y
contains 2 additional class implementations, being the part it inherited from OtherClass
and the part it inherited from X
. When implicitly casting to X*
(which is allowed) the address has to change of course to point to the X
-part of Y
as X does not know of OtherClass
.
Since Y*
is convertible to X*
the code works fine. When you call the out()
with A*
then B::out()
return value is resolved to X*
only.
Because of implicit conversion from Y*
to X*
you don't notice the change or problem of return
type. Just try following 2 scenarios and your code will stop working:
- changing
X*
toint*
- make the inheritance from
X
toY
asprivate
/protected
Depending on how OtherClass looks like, the values held by pointers x and y may indeed be numerically different, although they point to the same object - the cause of this (for many people surprising) fact is the multiple inheritance. To understand the reason why that might be you must consider the memory layout of objects of class Y (see also Wikipedia entry on VTable). Lets assume that class OtherClass looks like this:
class OtherClass
{
public:
virtual ~OtherClass() {}
};
Then an instance of class Y might, depending on your system and compiler, look like this:
0x0000 ... 4 bytes - int x (inherited from class X)
0x0004 ... 4 bytes - pointer to vtable of class Y (for OtherClass base)
On the following line of your code, a conversion from a pointer to Y to a pointer to X takes place:
X* x = a->out();
The resulting address stored in x now points 4 bytes before address stored in y. Because the compiler knows the memory layout, it also knows the offsets to use when converting between covariant types and places an appropriate additions/subtractions into the compiled file. Note however that even though the pointers numerically differ, they will be converted to the common type when compared and such comparison will return 1.
精彩评论