您遇到的问题与存储分配有关。分配数组时,它们需要包含所有元素的存储空间。让我举一个(高度简化的)例子。假设您有这样的课程设置:
class Base
{
public:
int A;
int B;
}
class ChildOne : Base
{
public:
int C;
}
class ChildTwo : Base
{
public:
double C;
}
当您分配 aBase[10]
时,数组中的每个元素将需要(在典型的 32 位系统*上)8 字节的存储空间:足以容纳两个 4 字节的整数。但是,一个ChildOne
类需要其父类的 8 个字节的存储空间,以及其成员的额外 4 个字节C
。一个ChildTwo
类需要其父类的 8 个字节,再加上其double C
. 如果您尝试将这两个子类中的任何一个推送到分配给 8-byte 的数组中Base
,您最终会溢出存储空间。
指针数组起作用的原因是它们的大小是恒定的(在 32 位系统上每个 4 个字节),无论它们指向什么。指向 aBase
的指针与指向 a 的指针相同ChildTwo
,尽管后者的大小是后者的两倍。
该dynamic_cast
运算符允许您执行类型安全的向下转换以将 更改Base*
为 a ChildTwo*
,因此它将在这种特殊情况下解决您的问题。
或者,您可以通过创建类似这样的类布局来将处理逻辑与数据存储(策略模式)分离:
class Data
{
public:
int A;
int B;
Data(HandlerBase* myHandler);
int DoSomething() { return myHandler->DoSomething(this) }
protected:
HandlerBase* myHandler;
}
class HandlerBase
{
public:
virtual int DoSomething(Data* obj) = 0;
}
class ChildHandler : HandlerBase
{
public:
virtual int DoSomething(Data* obj) { return obj->A; }
}
这种模式适用于算法逻辑DoSomething
可能需要大量对象共有的重要设置或初始化(并且可以在ChildHandler
构造中处理)但不通用(因此不适合静态成员)的情况. 然后数据对象保持一致的存储并指向将用于执行其操作的处理程序进程,当它们需要调用某些东西时将它们自己作为参数传递。Data
这种类型的对象具有一致的、可预测的大小,并且可以分组到数组中以保持引用局部性,但仍然具有通常继承机制的所有灵活性。
请注意,您仍在构建相当于指针数组的内容 - 它们只是位于实际数组结构下方的另一层。
* 对于挑剔者:是的,我意识到我为存储分配提供的数字忽略了类头、vtable 信息、填充和大量其他潜在的编译器注意事项。这并不意味着详尽无遗。
编辑第二部分:以下所有材料都不正确。我在没有测试的情况下将它从头顶上发布,并将 reinterpret_cast 两个不相关的指针的能力与转换两个不相关的类的能力混淆了。Mea culpa,感谢 Charles Bailey 指出我的失态。
一般效果仍然是可能的——你可以强行从数组中抓取一个对象并将其用作另一个类——但它需要获取对象地址并强制将指针强制转换为新的对象类型,这违背了理论的目的避免指针取消引用。无论哪种方式,我最初的观点 - 这是一个可怕的“优化”,首先要尝试进行 - 仍然成立。
编辑:好的,我认为通过您的最新编辑,我已经弄清楚了您要做什么。我将在这里给你一个解决方案,但是,为了所有神圣的爱,请向我发誓,你永远不会在生产代码中使用它。这是一种工程好奇心,而不是一个好的做法。
您似乎试图避免进行指针取消引用(可能作为性能微优化?),但仍然希望在对象上调用子方法的灵活性。如果您确定您的基类和派生类的大小相同——您要知道这一点的唯一方法是检查编译器生成的物理类布局,因为它可以根据需要进行各种调整,并且规范没有给你任何保证——那么你可以使用 reinterpret_cast 强制将父级视为数组中的子级。
class Base
{
public:
int A;
int B;
void DoSomething();
}
class Derived : Base
{
void DoSomething();
}
void DangerousGames()
{
// create an array of ten default-constructed Base on the stack
Base items[10];
// force the compiler to treat the bits of items[5] as a Derived,
// and make a ref
Derived& childItem = reinterpret_cast<Derived>(items[5]);
// invoke Derived::DoSomething() using the data bits of items[5],
// since it has an identical layout
childItem.DoSomething();
}
这将为您节省指针取消引用,并且没有性能损失,因为 reinterpret_cast 不是运行时强制转换,它本质上是一个编译器覆盖,它说:“无论你认为你知道什么,我都知道我在做什么,闭嘴并做吧。” “轻微的缺点”是它使您的代码非常脆弱,因为对Base
or布局的任何更改Derived
,无论是您还是编译器发起的,都会导致整个事情在火焰中崩溃,可能会发生什么极其微妙且几乎不可能调试未定义的行为。同样,永远不要在生产代码中使用它。即使在性能最关键的实时系统中,指针取消引用的成本也总是与在您的代码库中间构建相当于触发核弹的东西相比,这是值得的。