我在一个翻译单元中有一堆复杂的类,其中涉及一堆标头依赖项。此外,翻译单元还提供工厂功能。
// 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
。不管我做什么。我无法跨翻译单元获取此信息。
那么如何实现我的目标呢?我是否需要通过构建系统以某种方式从目标文件中提取大小并从中生成标头?毕竟这可能没问题。
编辑1:一些澄清:
- 所有这些派生自 Interface.h 的类都在末尾的预链接、自包含静态库中定义。
- 通过"内部依赖",我的意思是我不想泄露给库消费者的其他头文件和类型
- 库的使用者可以创建各种类的多个实例。
创建第二个函数来返回类的大小:
extern size_t sizeofMyClass();
size_t sizeofMyClass() { return sizeof(MyClass); }
如果你在编译时想要它,那就constexpr
它。
您可以在"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[];
,并且不需要大小。
那么如何实现我的目标呢?
附录 1:静态断言和"gueess"大小。interface.h
和类之间没有依赖关系,但您必须在每次更改时手动更新标头(或者,更好的是从构建系统生成标头)。
// 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系统。文件描述符很简单 - 它只是一个数组中的一个索引...什么东西。实现起来很简单,您可以将所有内容隐藏在单个整数值后面。这基本上意味着,您必须使用动态内存,或者您必须提前知道需要多少对象并为所有这些对象分配内存。
下面的伪代码实现只返回指向接口的指针,但它与返回数组中的索引非常相似,就像文件描述符一样。
// 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
}
这是回答这个问题的第二次尝试。
你不能做你想做的事。隐藏实现 (PIMPL) 的唯一原因是允许您完全独立地编译模块。 您不能这样做,同时注入依赖项。你的要求是自相矛盾的。
要么在标头中声明类,以便声明依赖项,要么将数组放在包含该类的模块中。如果它是一个静态数组,为什么在哪个模块中声明它很重要。
换句话说,使用单例,可通过自由函数访问