如何根据来自另一个翻译单元的类型信息静态分配内存

How to statically allocate memory based upon type information from another translation unit

我在一个翻译单元中有一堆复杂的 类,其中涉及一堆 header 依赖项。另外,翻译单元提供了工厂功能。

// MyClass.h

#include "Interface.h"
// lots of other includes

class MyClass : public Interface {
  // lots of members
}:

// Creates an instance of MyClass using placement new
Interface* createMyClassAt(uint8_t* location);

在另一个翻译单元中,我想使用从 Interface 派生的不同 类 的多个实例,我想将它们分配到静态内存中。这是一个没有堆的小型嵌入式系统。我想避免包含 MyClass.h 因为它的某些依赖项是内部的。

// somefile.cpp
#include "Interface.h"

extern Interface* createMyClassAt(uint8_t* location);

uint8_t myClassContainer[sizeofMyClass];

int main() {
    createMyClassAt(myClassContainer);
    // more stoff
}

我的理解是,如果没有 MyClass 的实际类型信息,就不可能确定 sizeofMyClass。不管我做什么。我无法跨翻译单元获取此信息。

那怎么实现我的目标呢?我是否需要通过构建系统并以某种方式从 object 文件中提取大小并从中生成 header?毕竟那可能没问题。

编辑 1:一些说明:

创建第二个函数,returns 大小为 class:

extern size_t sizeofMyClass();


size_t sizeofMyClass() { return sizeof(MyClass); }

如果你在编译时想要它,那么constexpr它。

这是第二次尝试回答这个问题。

你不能为所欲为。隐藏实现 (PIMPL) 的唯一原因是允许您完全独立地编译模块。 您不能这样做,同时注入依赖项。你要的是self-contradictory.

要么在header中声明class,以便声明依赖关系,要么将数组放入包含class的模块中。如果它是一个静态数组,为什么在哪个模块中声明它很重要。

换句话说,使用 singleton,可通过 free-function

访问

您可以在“someFile”翻译单元中声明myClassContainer,但实际上在“myClass”翻译单元中定义它。使用通用头文件可以使这更容易:

// globaldefs.h
#include <cstdint>

extern std::uint8_t myClassContainer[];
#ifdef MY_CLASS_DEFINED
alignas(MyClass) std::uint8_t myClassContainer[sizeof(MyClass)];
#endif
// MyClass.h
class MyClass : public Interface {
  // lots of members
};

#define MY_CLASS_DEFINED
#include "globaldefs.h"
// somefile.cpp
#include "globaldefs.h"

extern Interface* createMyClassAt(uint8_t* location);

int main() {
    createMyClassAt(myClassContainer);
    // more stuff
}

这样,带有somefile.cpp的翻译单元只看到std::uint8_t myClassContainer[];,不需要大小。

How to achieve my goal then?

方法 1:静态断言和“猜测”大小。 interface.h 和 class 之间没有依赖关系,但是您必须 手动 更新每个更改的 header (或者,更好的是,生成header 来自构建系统)。

// interface.h
using Interface_storage = std::aligned_storage<20, 16>;
//                                              ^^^^^^ - size and alignment
// They are _hard-coded_ here and _need_ to be _manually_ updated each time
// MyClass changes.
Interface* createMyClassAt(Interface_storage& location);

// interface.c
Interface* createMyClassAt(Interface_storage& location) {
   // static assertion check
  static_assert(sizeof(MyClass) == sizeof(Interface_storage) &&
     alignof(MyClass) == alignof(Interface_storage), 
    " Go and fix the header file");
  // use placement new on location
}

// main.c
int main() {
  Interface_storage storage;    // nice&clean
  Interface *i = createMyClassAt(storage);
  destroyMyClassAt(i, storage);
}

方法 2:Unix 系统自古以来就使用文件描述符。一个文件描述符很简单——它只是一个数组中的一个索引……一些东西。实现起来很简单,您可以将 一切 隐藏在单个整数值后面。这基本上意味着,您必须使用动态内存,或者您必须提前知道您需要多少 objects 并为所有这些内存分配内存。

下面的伪代码实现只是 returns 指向接口的指针,但它非常类似于返回数组中的索引,就像文件描述符一样。

// interface.h
Interface *new_MyClass();
destroy_MyClass(Interface *);

// interface.c
#define MAX 5
std::array<std::aligned_storage<sizeof(MyClass), alignof(MyClass)>, MAX> arr;
std::array<bool, MAX> used;

Interface *new_MyClass() {
   // find the fist not used and return it.
   for (size_t i = 0; i < used.size(); ++i) {
      if (!used[i]) {
         used[i] = true;
         return new(arr[i]) MyClass();
       }
   }
   return nullptr;
}

void destroy_MyClass(Interface *i) {
   // update used array and destroy
}