[英]Subclass address equal to virtual base class address?
我们都知道,在使用简单单继承时,派生类的地址与基类的地址相同。 多重继承使这一点变得不真实。
虚拟继承是否也使这不真实? 换句话说,以下代码是否正确:
struct A {};
struct B : virtual A
{
int i;
};
int main()
{
A* a = new B; // implicit upcast
B* b = reinterpret_cast<B*>(a); // fishy?
b->i = 0;
return 0;
}
我们都知道,在使用简单单继承时,派生类的地址与基类的地址相同。
我认为这种说法是不正确的。 在下面的代码中,我们有一个简单的(非虚拟的)单(非多重)继承,但地址不同。
class A
{
public:
int getX()
{
return 0;
}
};
class B : public A
{
public:
virtual int getY()
{
return 0;
}
};
int main()
{
B b;
B* pB = &b;
A* pA = static_cast<A*>(pB);
std::cout << "The address of pA is: " << pA << std::endl;
std::cout << "The address of pB is: " << pB << std::endl;
return 0;
}
VS2015 的输出是:
The address of pA is: 006FF8F0
The address of pB is: 006FF8EC
虚拟继承是否也使这不真实?
如果将上面代码中的继承改为virtual,结果也是一样的。 因此,即使在虚拟继承的情况下,基对象和派生对象的地址也可能不同。
reinterpret_cast<B*>(a);
只保证点给封闭B
的对象a
,如果a
子对象和包围B
对象是指针的互相转化,见[expr.static.cast] / 3的C ++ 17标准的。
仅当派生对象是标准布局、不具有直接的非静态数据成员并且基类对象是其第一个基类子对象时,派生类对象才能与基类对象进行指针互转换。[基本化合物]/4.3
拥有virtual
基类会使类不符合标准布局。 [类]/7.2 .
因此,因为B
有一个虚拟基类和一个非静态数据成员, b
不会指向封闭的B
对象,而是b
的指针值将与a
的值保持不变。
访问i
成员就好像它指向B
对象那样具有未定义的行为。
任何其他保证将来自您的特定 ABI 或其他规范。
多重继承使这一点变得不真实。
这并不完全正确。 考虑这个例子:
struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};
创建D
的实例时, B
和C
分别使用其各自的A
实例进行实例化。 但是,如果D
的实例具有其B
实例及其各自的A
实例的相同地址,则没有问题。 虽然不是必需的,但这正是使用clang 11
和gcc 10
编译时发生的情况:
D: 0x7fffe08b4758 // address of instance of D
B: 0x7fffe08b4758 and A: 0x7fffe08b4758 // same address for B and A
C: 0x7fffe08b4760 and A: 0x7fffe08b4760 // other address for C and A
虚拟继承是否也使那不真实
让我们考虑上述示例的修改版本:
struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};
使用virtual
函数说明符通常用于避免模糊的函数调用。 因此,在使用virtual
继承时, B
和C
实例都必须创建一个公共的A
实例。 实例化D
,我们得到以下地址:
D: 0x7ffc164eefd0
B: 0x7ffc164eefd0 and A: 0x7ffc164eefd0 // again, address of A and B = address of D
C: 0x7ffc164eefd8 and A: 0x7ffc164eefd0 // A has the same address as before (common instance)
以下代码是否正确
这里没有理由使用reinterpret_cast
,更重要的是,它会导致未定义的行为。 使用static_cast
代替:
A* pA = static_cast<A*>(pB);
在此示例中,两种类型转换的行为都不同。 reinterpret_cast
会将pB
reinterpret_cast
解释为指向A
的指针,但指针pA
可能指向不同的地址,如上例(C vs A)。 如果您使用static_cast
,指针将被正确向上转换。
在您的情况下a
和b
不同的原因是因为A
没有任何虚拟方法,因此A
没有维护vtable
。 另一方面, B
确实维护了一个vtable
。
当您向上转换为A
,编译器足够聪明,可以跳过用于B
的vtable
。 因此地址的差异。 你不应该reinterpret_cast
回B
,它不会工作。
要验证我的主张,请尝试添加一个virtual
方法,例如在class A
添加virtual void foo() {}
。 现在A
还将维护一个vtable
。 因此 downcast( reinterpret_cast
) 到 B 会让你回到原来的b
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.