具有多重继承的协变返回类型。这段代码是如何工作的?

时间:2011-07-28 09:36:15

标签: c++ multiple-inheritance covariance

任何人都可以告诉我返回类型协方差如何在以下代码中起作用?

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.然后是什么时候完成转换呢?

5 个答案:

答案 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 codeX*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具有不同的地址是正常的,因为它是两个不同的指针。它们确实具有相同的值,这是它们所指向的变量的地址。

编辑:您可以使用此主要来检查我的意思,第一行将打印xy的值(即他们指向的地址),它们应始终是同样的,因为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类型的更改或问题。只需尝试以下两种方案,您的代码就会停止工作:

  1. X*更改为int*
  2. 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.