在堆栈上分配不完整的类型

时间:2016-07-12 04:44:16

标签: c++ c memory-management types

我正在用C包装C ++库.C ++库是数据库服务器的库。它使用包装类来传递序列化数据。我不能直接在C中使用该类,所以我定义了一个可以在C代码中使用的结构,如下所示:

include/c-wrapper/c-wrapper.h中(这是我的C包装器的客户端包含的包装器)

extern "C" {
    typedef struct Hazelcast_Data_t Hazelcast_Data_t;

    Hazelcast_Data_t *stringToData(char *str);
    void freeData(Hazelcast_Data_t *d);
}

impl.pp

extern "C" struct Hazelcast_Data_t {
    hazelcast::client::serialization::pimpl::Data data; // this is the C++ class
};

Hazelcast_Data_t *stringToData(char *str) {
     Data d = serializer.serialize(str);

     Hazelcast_Data_t *dataStruct = new Hazelcast_Data_t();
     dataStruct->data = d;

     return dataStruct;
}

...

现在这个工作,我的C库的客户端只看到typedef struct Hazelcast_Data_t Hazelcast_Data_t;。问题是,上述类型不能在堆栈上分配,就像我想提供这样的API:

// this is what I want to achieve, but Hazelcast_Data_t is an incomplete type
#include <include/c-wrapper/c-wrapper.h>

int main() {
    char *str = "BLA";
    Hazelcast_Data_t d;
    stringToData(str, &d);
}

编译器将抛出Hazelcast_Data_t为不完整类型的错误。我仍然想提供一个允许将Hazelcast_Data_t的堆栈分配引用传递给序列化函数的API,但由于Hazelcast_Data_t有一个指向C ++类的指针,这似乎是不可能的。但是,选择传递堆栈分配的引用会大大简化我的C库客户端的代码(不需要释放new ed结构)。

以某种方式重新定义Hazelcast_Data_t类型是否可行,以便它可以在C中使用并仍然在堆栈中分配?

3 个答案:

答案 0 :(得分:3)

您正在考虑执行此操作的大多数黑客调用未定义的行为,因为在创建结构时C不会为包含的对象调用C ++构造函数,而在结构超出范围时不调用C ++析构函数。为了使它工作,你需要struct在init函数中包含一个正确大小的缓冲区和new缓冲区,并在完成后调用该缓冲区上的析构函数。这意味着代码看起来像这样(假设没有抛出 - 在这种情况下你需要添加异常处理和翻译......)

struct wrapper {
  char buffer[SIZE_OF_CXX_CLASS];
}

void wrapper_init() {
   new (buffer) Wrapped();
}

void wrapper_destroy() {
   ((Wrapper*)buffer)->~Wrapper();
}

{
  struct wrapper wrapped;
  wrapper_init(&wrapped);
  // ... use it ...
  wrapper_destroy(&wrapped);
}

如果你忘记致电wrapper_init,一切都会进入未定义的behvaiour土地。如果您忘记致电wrapper_destroy我认为您也会获得UB。

但是由于这会强制你的调用者调用init并销毁函数,所以使用指针几乎没什么好处。我甚至声称使用结构而不是指针向API用户建议初始化应该是微不足道的,并且不需要破坏。即作为我希望能够做到的API用户

 {
   struct wrapper wrapped = WRAPPER_INIT; //Trivial initialisaton macro
   // .. use it ..
   // No need to do anything it is a trivial object.
 }

在无法做到这一点的情况下(和你的一样)我会坚持使用通常的在堆上分配成语

{
   struct wrapper* wrapped = wrapper_create();
   // ... use it ...
   wrapper_destroy(wrapped);
}

答案 1 :(得分:1)

您需要在头文件中提供结构的定义,以便客户端知道在堆栈上分配多少空间。但是当C ++类中的基础表示不能被extern "C"公开时,这变得棘手。

解决方案是指向C ++类而不是实际类的指针。由于指针的大小相同,因此即使它不了解C ++,也可以在C客户端中使用。

因此在标题

typedef struct Hazelcast_Data_t {
       void *data
} Hazelcast_Data_t

在C ++文件中,您可以使用static_cast通过此指针访问C ++类。

答案 2 :(得分:0)

创建一个包装器结构,它只包含一个大的数组,并且对齐到足以包含C ++类型。在其中放置新的C ++类型。

您可能需要构建一个小型C ++可执行文件,该文件将生成一个CIZ文件文件,其中SIZEOF_HAZELCAST_T和ALIGNOF_HAZELCAST_T已正确定义。