子类地址等于虚拟基类地址?
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
虚拟继承是否也使这变得不真实?
如果将上述代码中的继承更改为虚拟,则结果将是相同的。 因此,即使在虚拟继承的情况下,基对象和派生对象的地址也可能不同。
如果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
的实例时,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
重新解释为指向A
的指针,但指针pA
可能指向不同的地址,如上例(C vs A(。如果使用static_cast
,指针将正确向上转换。
在您的情况下,a
和b
不同的原因是,由于A
没有任何虚拟方法,A
没有维护vtable
。另一方面,B
确实保持了vtable
。
当你向上转换为A
时,编译器足够聪明,可以跳过B
的vtable
。因此地址的差异。你不应该reinterpret_cast
回到B
,这是行不通的。
要验证我的声明,请尝试添加virtual
方法,例如virtual void foo() {}
inclass A
。现在A
也将保持vtable
。因此,向下(reinterpret_cast
(到B会给你原来的b
。
- std::具有相同基类的类的变体
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 基类中的函数名称解析
- C++初始化基类
- 如何通过派生类函数更改基类中的向量
- 如何定义一个纯抽象基类
- 如何使用基类指针引用派生类成员
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 子类地址等于虚拟基类地址?
- 如果我有指向基类对象的指针,如何获取虚拟方法的地址?
- 使用成员的地址初始化基类是否合法?
- 通过C 中的构造函数将地址参数传递给基类指针
- 通过指向非多态类型的基类的指针获取已分配内存的地址
- 尝试将派生类对象地址分配给基类指针的向量
- 当基类不是多态但派生时,"this"地址不匹配是
- 基类的constexpr地址
- 使用派生类成员的地址定义基类成员指针
- 为什么基类指针总是指向基类,即使它持有派生类对象地址
- 如何在继承的类成员函数中访问基类成员的地址