任何人都可以告诉我返回类型协方差如何在以下代码中起作用?
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();
}
编辑: 对不起,我一定不够清楚。 x和y指向不同的地址就像我期望的那样,因为涉及多个继承,因此X和Y对象不在同一地址上。 我的问题是这个演员何时完成? out()函数不能这样做,因为它总是从它的角度返回一个指向Y的指针。 out()的调用者不能这样做,因为它看到X *的具体类型可能是X或Y.然后是什么时候完成转换呢?
答案 0 :(得分:3)
什么时候完成演员表?
嗯,这是在B::out
返回后,A::out
来电结束之前完成的。真的没有多少。
多态调用返回的指针必须是X*
类型。这意味着它必须指向X
类型的对象。由于您使用的是多重继承,因此Y
个对象可以包含多个子对象。在您的情况下,他们可以拥有OtherClass
子对象和X
子对象。显然,这些子对象不存储在同一地址中。当你要求指向X*
的指针时,你得到一个指向X
子对象的指针,当你要求指向Y*
的指针时,你得到一个指向整个Y
对象的指针
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;
}
您可以看到running this code为X*
和Y*
指针生成不同的地址,为Y*
和OtherClass*
指针生成相同的地址,因为object在OtherClass
子对象之前与X
子对象一起布局。
x:0x804a15c
y:0x804a158
o:0x804a158
答案 1 :(得分:3)
通过“它是如何工作的”,我假设你在询问生成的是什么 代码看起来像。 (当然,在典型的实施中。我们都知道 生成的代码在实现之间可能会有所不同。)我是 意识到两种可能的实现:
编译器总是生成代码以返回指向基类的指针
类;即在B::out
中,编译器会将Y*
转换为X*
在回来之前。在呼叫站点,如果呼叫是通过
lvalue使用静态类型B
,编译器将生成代码
将返回的值重新转换为Y*
。
或者(我认为这种情况更常见,但我很远
确定),编译器生成thunk,所以当你调用a->out
时,
被调用的虚函数不是直接B::out
,而是一个小函数
包装器,用于将从Y*
返回的B::out
转换为X*
。
g ++和VC ++似乎都使用了thunk(从很快的一瞥)。
答案 2 :(得分:1)
注意:第一个答案仅适用于单继承,下面的多重继承(edit2)。
x和y具有不同的地址是正常的,因为它是两个不同的指针。它们确实具有相同的值,这是它们所指向的变量的地址。
编辑:您可以使用此主要来检查我的意思,第一行将打印x
和y
的值(即他们指向的地址),它们应始终是同样的,因为out
是虚拟的,实际上会调用相同的方法。第二个将打印自己的地址,这当然是不同的,因为它们是不同的(指针)变量。
#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:好的,这是错的,我道歉。当多重继承发挥作用时,从Y*
到X*
的隐式转换(因此在分配x时,而不是out
的返回)将改变指针地址。
这是因为在实现时,Y
的布局包含2个额外的类实现,它是从OtherClass
继承的部分,以及它从X
继承的部分。当隐式转换为X*
(允许)时,地址必须更改为指向X
- Y
的一部分,因为X不知道OtherClass
。
答案 3 :(得分:0)
由于Y*
可转换为X*
,因此代码运行正常。当您使用out()
致电A*
时,B::out()
返回值仅解析为X*
。
由于从Y*
到X*
的隐式转换,您没有注意到return
类型的更改或问题。只需尝试以下两种方案,您的代码就会停止工作:
X*
更改为int*
X
继承为Y
/ private
答案 4 :(得分:0)
根据OtherClass的外观,指针x和y所保持的值可能确实在数值上不同,尽管它们指向同一个对象 - 这个原因(对于许多人来说令人惊讶)事实是多重继承。要了解可能的原因,您必须考虑Y类对象的内存布局(另请参阅Wikipedia entry on VTable)。让我们假设类OtherClass看起来像这样:
class OtherClass
{
public:
virtual ~OtherClass() {}
};
然后,根据您的系统和编译器,类Y的实例可能如下所示:
0x0000 ... 4字节 - 整数x(从类X继承)
0x0004 ... 4个字节 - 指向Y类vtable的指针(对于OtherClass base)
在代码的以下行中,从指针Y转换为指向X的指针:
X* x = a->out();
存储在x中的结果地址现在指向存储在y中的地址之前的4个字节。因为编译器知道内存布局,所以它还知道在协变类型之间进行转换时要使用的偏移量,并在编译文件中放置适当的加法/减法。但请注意,即使指针在数值上有所不同,它们也会在比较时转换为通用类型,这种比较将返回1.