子类地址等于虚拟基类地址?

Subclass address equal to virtual base class address?

本文关键字:地址 基类 虚拟 于虚拟 子类      更新时间:2023-10-16

我们都知道,当使用简单的单一继承时,派生类的地址与基类的地址相同。 多重继承使这变得不真实。

虚拟继承是否也使这变得不真实?换句话说,以下代码是否正确:

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

虚拟继承是否也使这变得不真实?

如果将上述代码中的继承更改为虚拟,则结果将是相同的。 因此,即使在虚拟继承的情况下,基对象和派生对象的地址也可能不同。

如果a子对象和封闭B对象是指针可相互转换的,则仅保证reinterpret_cast<B*>(a);的结果指向a的封闭B对象,请参阅 C++17 标准的 [expr.static.cast]/3。

仅当派生对象是标准布局、没有直接的非静态数据成员并且基类对象是其第一个基类子对象时,派生类对象才可与基类对象进行指针互转换。[基本化合物]/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的实例时,BC都使用各自的A实例进行实例化。但是,如果D的实例与其B实例及其各自的A实例具有相同的地址,则不会有问题。虽然不是必需的,但这正是使用clang 11gcc 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重新解释为指向A的指针,但指针pA可能指向不同的地址,如上例(C vs A(。如果使用static_cast,指针将正确向上转换。

在您的情况下,ab不同的原因是,由于A没有任何虚拟方法,A没有维护vtable。另一方面,B确实保持了vtable

当你向上转换为A时,编译器足够聪明,可以跳过Bvtable。因此地址的差异。你不应该reinterpret_cast回到B,这是行不通的。

要验证我的声明,请尝试添加virtual方法,例如virtual void foo() {}inclass A。现在A也将保持vtable。因此,向下(reinterpret_cast(到B会给你原来的b