c++ - 如何在C++中将对象的类更改为其当前类的子类?

标签 c++ class type-conversion subclassing

我有一个指向基类的指针数组,因此我可以使这些指针指向基类的(不同)子类,但仍与它们交互。 (实际上只有几个使我成为虚拟的和重载的方法)我想知道我是否可以避免使用指针,而是只创建基类的数组,但是有某种方式将类设置为我的子类选择。我知道必须有某些东西用来指定类,因为它需要使用它来查找虚拟方法的函数指针。顺便说一下,所有子类都具有相同的ivars和布局。

注意:由于性能提高,设计实际上基于使用模板参数而不是变量,因此,实际上,抽象基类只是子类的接口(interface),除了它们的已编译代码外,它们都是相同的。

谢谢

编辑:所有子类(如果需要的话)都具有相同的布局/大小

顺便说一句,策略模式会很好,但是它添加了一个指向类的指针,而我试图避免的只是一种尊重。

简单来说,我想做的是

class base {
int ivars[100];
public:
virtual char method() = 0;
}

template <char c>
class subclass : base {
public:
  char method() {
    return c;
  }
}

base things[10];
some_special_cast_thingy(things[2], subclass);
printf("%c", things[2].method());

显然,它要复杂得多,但是就类(class)/所学内容而言,这几乎是我想要的全部。顺便说一句,这可能是语言功能。

最佳答案

您遇到的问题与存储分配有关。分配数组后,它们需要包含所有元素的存储。让我举一个(高度简化的)示例。假设您已经建立了这样的类(class):

class Base
{
public:
  int A;
  int B;
}

class ChildOne : Base
{
public:
  int C;
}

class ChildTwo : Base
{
public:
  double C;
}

分配Base[10]时,数组中的每个元素(在典型的32位系统上*)将需要8字节的存储空间:足以容纳两个4字节的整数。但是,ChildOne类需要其父级存储8个字节,并为其成员C增加4个字节。 ChildTwo类需要其父级的8个字节,以及其double C所需要的另外8个字节。如果您尝试将这两个子类中的任何一个推入分配给8字节Base的数组中,则会导致存储溢出。

指针数组起作用的原因是它们的大小是恒定的(在32位系统上每个为4字节),无论它们指向什么。指向Base的指针与指向ChildTwo的指针相同,尽管后者的大小是其两倍。
dynamic_cast运算符允许您执行类型安全的向下转换,以将Base*更改为ChildTwo*,因此它将在这种特殊情况下解决您的问题。

另外,您可以通过创建类似以下的类布局,将处理逻辑与数据存储(Strategy Pattern)分离:
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构造中进行处理)但不通用(因此不适用于静态)的情况下,此模式将是适当的成员(member))。然后,数据对象保持一致的存储,并指向将用于执行其操作的处理程序进程,并在需要调用某些内容时将其作为参数传递。这种Data对象具有一致的,可预测的大小,可以分组为数组以保留引用局部性,但仍具有常规继承机制的所有灵活性。

请注意,您仍在构建相当于一个指针数组的东西,它们只是嵌套在实际数组结构下方的另一层。

*对于nitpickers:是的,我意识到为存储分配提供的数字忽略了类头,vtable信息,填充以及许多其他潜在的编译器注意事项。这并不是要详尽无遗。

编辑第二部分:以下所有 Material 都不正确。我没有测试就将其发布在我的头上,并且将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不是运行时强制转换,它本质上是编译器重写,它表示:“无论您认为什么,我都知道我在做什么,请关闭并去做。” “轻微的缺点”是它使您的代码非常脆弱,因为无论是由您还是由编译器发起的BaseDerived布局的任何更改,都将导致整个事件崩溃,甚至可能非常微妙,几乎无法调试未定义的行为。同样,永远不要在生产代码中使用它。即使在对性能至关重要的实时系统中,指针取消引用的成本也等于,而与在代码库中间构建相当于触发原子弹的炸弹相比,这始终值得

关于c++ - 如何在C++中将对象的类更改为其当前类的子类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2531202/

相关文章:

c++ - C++ 中的函数拆分?

java - 如何使用带有 Class 值的 JComboBox

java - 为什么 DozerConverter 不工作?

c++ - 在 Boost::Geometry::Polygon 中找到一个点

c++ - 只有一个没有主体的函数的类的大小

c++ - Lisp 作为 C++ 应用程序中的脚本语言

arrays - 将 C 字符串数组转换为 Swift 字符串数组

Java q关于类结构

c++ - 不同类的相同功能包含在主 C++ 中

matlab - 如何将二进制值列表转换为int32类型?