没有指向基类数据的特殊指针。C++ 中数据成员的布局与 C 密切相关。(事实上,您的示例没有虚拟方法,因此必须完全遵循 C)。因此,让我们考虑一下您的代码在 C 中的外观:
struct Base
{
int anint;
float afloat;
};
struct Derived
{
struct Base; // C style inherit from struct Base
int a2ndInt;
};
在 C 中,结构内存布局被定义为与您编写它时几乎一样。这意味着结构 Derived 基本上具有以下内存布局。
struct Derived
{
int anint;
float afloat;
int a2ndInt;
};
this指针指向结构的开头,因此从指向 Derived 或 Base 的指针访问anint或 afloat 涉及相同的内存偏移量。因此向下铸造在这里总是很容易。
当你有虚函数时事情会变得更复杂,因为数据结构必须有一个指向它的虚函数的隐藏指针,但只需要一个这样的指针。让我们考虑单继承的情况,你可能会想像这样的布局(实际布局取决于 ABI):
struct Base
{
<ABI defined pointer type> * class_; // hidden virtual function table
int anint;
float afloat;
};
struct Derived
{
struct Base; // inherit from struct Base
int a2ndInt;
};
现在 struct Derived 可能具有以下内存布局。请注意,在创建 Derived 对象时,构造函数必须设置class_指针。这是构造函数从基类构造函数开始的原因之一,因为每个派生类都可以覆盖class_指针。
struct Derived
{
<ABI defined pointer type> * class_;
int anint;
float afloat;
int a2ndInt;
};
因此,再次从指向 Derived 或 Base 的指针访问anint或afloat涉及相同的偏移量。因此向下铸造在这里又很容易。
多重继承要复杂得多,这也是向下转换的 static_cast<> 必不可少的地方。这种情况最接近您的想法,但仍然只涉及到单个this指针的偏移量。
struct Base1
{
<ABI defined pointer type> * class_; // hidden virtual function table
int anint;
};
struct Base2
{
<ABI defined pointer type> * class_; // hidden virtual function table
float afloat;
};
我对 ABI 不太熟悉,但我想隐藏的虚拟表指针可以以某种方式合并,从而导致内存布局类似于:
struct Derived
{
<ABI defined pointer type> * class_; // merged Base1 and Base2
int anint;
float afloat;
int a2ndInt;
};
或不合并(取决于 ABI)
struct Derived
{
<ABI defined pointer type> * class_; // from Base1
int anint;
<ABI defined pointer type> * class_; // from Base2
float afloat;
int a2ndInt;
};
因此,再次从指向 Derived 或 Base1 的指针访问anint涉及相同的偏移量,但访问浮点数不起作用。这意味着使用(Base2*)
从派生指针到 Base2 指针的 C 样式转换(即使用)失败,您需要static_cast<>
处理偏移量的变化。
请注意,如果只有一个基类具有成员数据,这并不是那么复杂。这就是为什么经常建议在使用多重继承时,只有一个基类应该有数据。
注意:真正的 ABI 定义了结构中数据的真实布局。此处显示的内容仅用于说明目的。