繁体   English   中英

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

[英]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的实例时, 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继承时, BC实例都必须创建一个公共的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 ,指针将被正确向上转换。

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

当您向上转换为A ,编译器足够聪明,可以跳过用于Bvtable 因此地址的差异。 你不应该reinterpret_castB ,它不会工作。

要验证我的主张,请尝试添加一个virtual方法,例如在class A添加virtual void foo() {} 现在A还将维护一个vtable 因此 downcast( reinterpret_cast ) 到 B 会让你回到原来的b

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM