静态分配继承对象的数组

Statically allocating array of inherited objects

本文关键字:数组 对象 继承 分配 静态      更新时间:2023-10-16

这个问题的标题非常复杂,所以我将尝试用一个例子来阐述它。假设我有一个抽象基类,它有许多从中继承的类。在下面的例子中,我只显示了两个继承的类,但实际上可能还有更多。

class Base {
public:
  Base();
  virtual ~Base() = 0;
/// Other methods/members
};
class SmallChild: public Base {
public:
  SmallChild();
  ~SmallChild();
/// Other methods/members such that sizeof(SmallChild) < sizeof(LargeChild)
};
class LargeChild : public Base {
public:
  LargeChild();
  ~LargeChild();
/// Other methods/members such that sizeof(LargeChild) > sizeof(SmallChild)
};

我需要实现一个容器,该容器最多存储N继承的对象。这些对象需要在运行时创建/销毁,并放置在容器中,但由于项目中的限制(特别是在嵌入式硬件上),动态内存分配不是一种选择。容器需要静态分配其所有空间。此外,编译器不支持C++11。

我只能想到一种方法来实现这一点。要引用N对象,我首先需要创建一个指向基类的指针数组,然后要实际存储这些对象,我需要创建足够大的缓冲区来存储最大继承对象的N副本,在本例中是LargeChild

Base * children[N];
uint8_t childBuffer[N * sizeof(LargeChild)];

然后,我可以将children中的指针分布在childBuffer中,每个指针由sizeof(LargeChild)分隔。由于需要创建对象,可以使用C++的"placement new"将它们放置在阵列中的指定位置。我需要跟踪childBuffer中每个对象的类型,以便取消引用children中的指针,但这应该不会太糟糕。

关于整个设置/实现,我有几个问题:

  1. 这是解决我所描述的问题的好方法吗?我以前从未实施过这样的事情,所以我不知道我是否会在这里吃午饭,还有一种更容易的方法来实现这一点。

  2. 这其中有多少可以在编译时完成?如果我有M类型的继承类(SmallChildLargeChild等),但我不知道它们的大小,如何确定childBuffer的大小?这个大小取决于最大类的大小,但有没有办法在编译时确定这个大小?我可以想象一些预处理器宏在类中迭代,评估sizeof并找到最大值,但我对这种级别的预处理器工作几乎没有经验,也不知道这会是什么样子。我也可以想象使用模板是可能的,但同样,我没有任何编译时模板魔法的经验,所以我只是基于我的直觉。任何关于如何实施这一点的指示都将不胜感激。

您需要能够解除对对象的定位吗?如果没有,则可能更容易覆盖operator new。我指的是:

void* operator new (std::size_t size) throw (std::bad_alloc);

所有的重写都会从一个非常大的缓冲区中分配内存。分配多少内存由size参数指定。

这样你就可以说

children[i] = new SmallChild();

编辑:如果确实需要解除分配,则需要更复杂的数据结构。无论如何,您最终都可能重新实现堆。

如果对象集是完全静态的(在构建时设置,在运行时不会更改),通常的方法是使用每个派生类的一组数组,并使用指向其他数组的指针构建"全局"数组:

static SmallChild small_children[] = {
    { ...initializer for first small child... },
    { ...initializer for second small child... },
    ...
};
static LargeChild large_children[] = {
    { ...initializer for first large child... },
    ...
};
Base *children[N] = { &small_children[0], &small_children[1], &large_children[0], ....

如果经常在构建中添加/删除子级,或者子级数组中的顺序很重要,那么维护这一点可能很困难。可能需要使用读取所需子项的描述的脚本或构建程序来生成上述源文件。

考虑到您的限制(即不使用动态分配),您的方法很有趣。

事实上,您正在以自己的方式管理一种union anyChild { smallChild o1; largeChild o2; ... };的阵列。sizeof(anyChild)将为您提供所需的最大块大小。

顺便说一句,在您的方法中可能存在悬挂指针的风险,只要所有对象都并没有用新的位置创建,或者其中一些对象是通过显式调用其析构函数删除的。

如果将派生类型放入联合中:

    union Child{
        SmallChild asSmallChild;
        LargeChild asLargeChild;
    }

然后,并集将自动具有最大类型的大小。当然,现在你有了一个新问题。工会代表什么类型?您可以在基类中给自己一个提示,也可以使Child成为一个包含提示的结构,然后在其中内联并集。例如,看看Espressif在githubs上为ESP32制作的组件,那里有很多很好的联合用途。

无论如何,当你去分配时,如果你分配了一个union'ed类型的数组,它将生成一个最大的子数组。。。因为工会就是这么做的。