为什么即使对象指针在多重继承中不同,情况也是一样的?
当使用多重继承时,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
这里 casted1
和 casted2
具有不同的值,这是合理的,因为它们指向不同的子对象。当虚函数被调用时,到基类的转换已经完成,编译器不知道它最初是一个最派生的类。不过这个每次都是一样的。这是怎么发生的?
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
当在虚函数调用中使用多重继承时,对虚函数的调用通常会转到调整
this
指针的“thunk”。在您的示例中,casted1
指针的 vtbl 条目不需要 thunk,因为CClass
的IDerived1
子对象恰好与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, thecasted1
pointer's vtbl entry doesn't need a thunk becuase theIDerived1
sub-object of theCClass
happens to coincide with the start of the CClass object (which is why thecasted1
pointer value is the same as theCClass
object
pointer).However, the
casted2
pointer to theIDerived2
sub-object doesn't coincide with the start of theCClass
object, so the vtbl function pointer actually points to a thunk instead of directly to theCClass::Common()
function. The thunk adjusts thethis
pointer to point to the actualCClass
object then jumps to theCClass::Common()
function. So it will always get a pointer to the start of theCClass
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".
当转换为不同类型时,字段的偏移量以及 vtable 中的条目必须位于一致的位置。采用
ICommonBase*
作为参数的代码不知道您的对象实际上是IDerived2
。但它仍然应该能够取消引用->foo
或调用虚拟方法bar()
。如果这些地址不在可预测的地址,则无法工作。对于单一继承的情况,这很容易得到正确的结果。如果
Derived
继承自Base
,你可以说Derived
的偏移0也是Base
的偏移0,Derived
特有的成员可以位于Base
的最后一个成员之后。对于多重继承,显然这是行不通的,因为Base1
的第一个字节不能也是Base2
的第一个字节。每个人都需要自己的空间。因此,如果您有这样一个继承自两个的类(称之为
Foo
),编译器可以知道对于类型Foo
,Base1
部分从偏移量 X 开始,Base2
部分从偏移量 Y 开始。当转换为任一类型时,编译器只需向this
添加适当的偏移量即可。当调用
Foo
的实际虚拟方法时,其中实现由Foo
提供,它仍然需要指向该对象的“真实”指针,以便它可以访问所有其成员,而不仅仅是基Base1
或Base2
的特定实例。因此,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 anIDerived2
. Yet it still should be able to dereference->foo
or call the virtual methodbar()
. 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 fromBase
, you can just say that offset 0 ofDerived
is also offset 0 ofBase
, and the members unique toDerived
can go after the last member ofBase
. For multiple inheritance obviously that can't work because the first byte ofBase1
can't also be the first byte ofBase2
. 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 typeFoo
, theBase1
part starts at offset X, and theBase2
part starts at offset Y. When casting to either type, the compiler can just add the appropriate offset tothis
.When an actual virtual method of
Foo
is called, where the implementation is provided byFoo
, 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 baseBase1
orBase2
. Hencethis
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.
如果您看到内存中的对象布局,它将是这样的:
也可以是其他方式..但只是为了提供一个想法..
您的
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:
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 toIDetived2
the casted pointer will be pointing to the v-pointer forIDerived2
which will be offset by sizeof(pointer) fromthis
pointer.G++ 4.3 中有相同的对象模型,如您所示,(参见 Naveen 的回答)说casted1和casted2具有不同的值。
此外,在 G++ 4.3 中,即使您使用残酷的转换:
结果也是一样的。
编译器非常聪明
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:
the result is the same.
Very clever the compiler