为什么即使对象指针在多重继承中不同,情况也是一样的?

发布于 2024-08-11 08:15:42 字数 1495 浏览 1 评论 0原文

当使用多重继承时,C++ 必须维护多个 vtable,这导致对公共基类有“多个视图”。

这是一个代码片段:

#include "stdafx.h"
#include <Windows.h>

void dumpPointer( void* pointer )
{
    __int64 thisPointer = reinterpret_cast<__int64>( pointer );
    char buffer[100];
   _i64toa( thisPointer, buffer, 10 );
    OutputDebugStringA( buffer );
    OutputDebugStringA( "\n" );
}

class ICommonBase {
public:
    virtual void Common() = 0 {}
};

class IDerived1 : public ICommonBase {
};

class IDerived2 : public ICommonBase {
};

class CClass : public IDerived1, public IDerived2 {
public:
    virtual void Common() {
        dumpPointer( this );
    }
    int stuff;
};

int _tmain(int argc, _TCHAR* argv[])
{
    CClass* object = new CClass();
    object->Common();
    ICommonBase* casted1 = static_cast<ICommonBase*>( static_cast<IDerived1*>( object ) );
    casted1->Common();
    dumpPointer( casted1 );

    ICommonBase* casted2 = static_cast<ICommonBase*>( static_cast<IDerived2*>( object ) );
    casted2->Common();
    dumpPointer( casted2 );

    return 0;
}

它产生以下输出:

206968 //CClass::Common this
206968 //(ICommonBase)IDerived1::Common this
206968 //(ICommonBase)IDerived1* casted1
206968 //(ICommonBase)IDerived2::Common this
206972 //(ICommonBase)IDerived2* casted2

这里 casted1casted2 具有不同的值,这是合理的,因为它们指向不同的子对象。当虚函数被调用时,到基类的转换已经完成,编译器不知道它最初是一个最派生的类。不过这个每次都是一样的。这是怎么发生的?

When using multiple inheritance C++ has to maintain several vtables which leads to having "several views" of common base classes.

Here's a code snippet:

#include "stdafx.h"
#include <Windows.h>

void dumpPointer( void* pointer )
{
    __int64 thisPointer = reinterpret_cast<__int64>( pointer );
    char buffer[100];
   _i64toa( thisPointer, buffer, 10 );
    OutputDebugStringA( buffer );
    OutputDebugStringA( "\n" );
}

class ICommonBase {
public:
    virtual void Common() = 0 {}
};

class IDerived1 : public ICommonBase {
};

class IDerived2 : public ICommonBase {
};

class CClass : public IDerived1, public IDerived2 {
public:
    virtual void Common() {
        dumpPointer( this );
    }
    int stuff;
};

int _tmain(int argc, _TCHAR* argv[])
{
    CClass* object = new CClass();
    object->Common();
    ICommonBase* casted1 = static_cast<ICommonBase*>( static_cast<IDerived1*>( object ) );
    casted1->Common();
    dumpPointer( casted1 );

    ICommonBase* casted2 = static_cast<ICommonBase*>( static_cast<IDerived2*>( object ) );
    casted2->Common();
    dumpPointer( casted2 );

    return 0;
}

it produces the following output:

206968 //CClass::Common this
206968 //(ICommonBase)IDerived1::Common this
206968 //(ICommonBase)IDerived1* casted1
206968 //(ICommonBase)IDerived2::Common this
206972 //(ICommonBase)IDerived2* casted2

here casted1 and casted2 have different values which is reasonable since they point to different subobjects. At the point when the virtual function is called the cast to the base class has been done and the compiler doesn't know that it was a most derived class originally. Still this is the same each time. How does it happen?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

微凉徒眸意 2024-08-18 08:15:42

当在虚函数调用中使用多重继承时,对虚函数的调用通常会转到调整 this 指针的“thunk”。在您的示例中,casted1 指针的 vtbl 条目不需要 thunk,因为 CClassIDerived1 子对象恰好与CClass 对象的开始(这就是 casted1 指针值与 CClass object 指针相同的原因)。

但是,指向 IDerived2 子对象的 casted2 指针与 CClass 对象的开头不一致,因此 vtbl 函数指针实际上指向 thunk 而不是直接指向 CClass::Common() 函数。 thunk 调整 this 指针以指向实际的 CClass 对象,然后跳转到 CClass::Common() 函数。因此,无论从哪种类型的子对象指针调用它,它总是会获得指向 CClass 对象开头的指针。

Stanley Lippman 的“Inside the C++ Object Model”对此有很好的解释本书,第 4.2 节“虚拟成员函数/MI 下的虚拟函数”。

When multiple inheritance is used in a virtual function call, the call to the virtual function will often go to a 'thunk' that adjusts the this pointer. In your example, the casted1 pointer's vtbl entry doesn't need a thunk becuase the IDerived1 sub-object of the CClass happens to coincide with the start of the CClass object (which is why the casted1 pointer value is the same as the CClass object pointer).

However, the casted2 pointer to the IDerived2 sub-object doesn't coincide with the start of the CClass object, so the vtbl function pointer actually points to a thunk instead of directly to the CClass::Common() function. The thunk adjusts the this pointer to point to the actual CClass object then jumps to the CClass::Common() function. So it will always get a pointer to the start of the CClass object, regardless of which type of sub-object pointer it might have been called from.

There's a very good explanation of this in Stanley Lippman's "Inside the C++ Object Model" book, section 4.2 "Virtual Member Functions/Virtual Functions Under MI".

骑趴 2024-08-18 08:15:42

当转换为不同类型时,字段的偏移量以及 vtable 中的条目必须位于一致的位置。采用 ICommonBase* 作为参数的代码不知道您的对象实际上是 IDerived2。但它仍然应该能够取消引用 ->foo 或调用虚拟方法 bar()。如果这些地址不在可预测的地址,则无法工作。

对于单一继承的情况,这很容易得到正确的结果。如果Derived继承自Base,你可以说Derived的偏移0也是Base的偏移0, Derived 特有的成员可以位于 Base 的最后一个成员之后。对于多重继承,显然这是行不通的,因为 Base1 的第一个字节不能也是 Base2 的第一个字节。每个人都需要自己的空间。

因此,如果您有这样一个继承自两个的类(称之为 Foo),编译器可以知道对于类型 FooBase1部分从偏移量 X 开始,Base2 部分从偏移量 Y 开始。当转换为任一类型时,编译器只需向 this 添加适当的偏移量即可。

当调用 Foo 的实际虚拟方法时,其中实现由 Foo 提供,它仍然需要指向该对象的“真实”指针,以便它可以访问所有其成员,而不仅仅是基 Base1Base2 的特定实例。因此,this 仍然需要指向“真实”对象。

请注意,其实现细节可能与描述的不同,这只是问题存在原因的高级描述。

When casted to a different type, the offsets of fields as well as entries in the vtable have to be in a consistent place. Code that takes an ICommonBase* as a parameter doesn't know that your object is really an IDerived2. Yet it still should be able to dereference ->foo or call the virtual method bar(). If these aren't at predictable addresses that has no way of working.

For the single inheritance case, this is easy to get right. If Derived inherits from Base, you can just say that offset 0 of Derived is also offset 0 of Base, and the members unique to Derived can go after the last member of Base. For multiple inheritance obviously that can't work because the first byte of Base1 can't also be the first byte of Base2. Each one needs its own space.

So if you had such a class that inherits from two (call it Foo), the compiler can know that for the type Foo, the Base1 part starts at offset X, and the Base2 part starts at offset Y. When casting to either type, the compiler can just add the appropriate offset to this.

When an actual virtual method of Foo is called, where the implementation is provided by Foo, it still needs the "real" pointer to the object, so that it can access all of its members, not just the particular instance of the base Base1 or Base2. Hence this still needs to point to the "real" object.

Note that the implementation details of this may be different than described, this is just a high level description of why the problem exists.

装纯掩盖桑 2024-08-18 08:15:42

如果您看到内存中的对象布局,它将是这样的:

v-pointer for IDerived1
v-pointer for IDerived2
....
....

也可以是其他方式..但只是为了提供一个想法..

您的 this 将始终指向对象的开头即 IDerived1 的 v 指针存储的位置。但是,当您将指针强制转换为 IDerived2 时,强制转换的指针将指向 IDerived2 的 v 指针,该指针将从 this 偏移 sizeof(pointer) 指针。

If you see the object layout in memory for this it will be something like this:

v-pointer for IDerived1
v-pointer for IDerived2
....
....

It can be otherway also..but just to give an idea..

Your this will always point to the start of the object i.e. where the v-pointer for IDerived1 is stored. However, when you cast the pointer to IDetived2 the casted pointer will be pointing to the v-pointer for IDerived2 which will be offset by sizeof(pointer) from this pointer.

讽刺将军 2024-08-18 08:15:42

G++ 4.3 中有相同的对象模型,如您所示,(参见 Naveen 的回答)说casted1和casted2具有不同的值。

此外,在 G++ 4.3 中,即使您使用残酷的转换:

ICommonBase* casted1 = (ICommonBase*)(IDerived1*)object; 
ICommonBase* casted2 = (ICommonBase*)(IDerived2*)object;

结果也是一样的。

编译器非常聪明

There's the same object model in G++ 4.3 as shown by you, (see answer of Naveen) say casted1 and casted2 have different values.

Furthermore, in G++ 4.3, even you use brutal casting:

ICommonBase* casted1 = (ICommonBase*)(IDerived1*)object; 
ICommonBase* casted2 = (ICommonBase*)(IDerived2*)object;

the result is the same.

Very clever the compiler

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文