堆栈

2016-07-12 177 views
2

我上包裹C中的C A 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中使用,并且仍然在栈上分配的?

+0

Hazelcast_Data_t是否确实包含指向C++类的指针,还是包含实际的C++结构体或类对象?如果它实际上只是一个指针,那么你应该没有太多问题,因为即使对于不完整的类型(即其总是4或8个字节,取决于你的系统),指针的大小也是众所周知的。您可能必须将指针存储为(void *),以便C编译器更易于理解,但除此之外,我不明白为什么它会出现问题。 –

+0

@JeremyFriesner Hazelcast_Data_t包含实际数据。我也认为我可以使用一个void指针,但是我不能将'Hazelcast_Data_t data'强制转换为'void *',我不能把它作为地址,因为它是一个本地堆栈变量。 – Max

+0

投票结束的人:请帮我理解为什么不清楚我的问题中提问的内容。我对C/C++编程还很陌生,可能使用了错误的术语来描述我的问题。投票结束“不清楚”的理由根本不能帮助我。 – Max

回答

3

你正在考虑的大部分黑客都会调用未定义的行为,因为当struct被创建时,C不会调用包含对象的C++构造函数,并且当结构不再调用C++析构函数时范围。为了使它工作,你需要在init函数中包含一个正确大小的缓冲区和新的缓冲区,并在完成时调用该缓冲区的析构函数。这意味着代码如下(假设没有抛出 - 在这种情况下,你需要添加异常处理和翻译......)

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和destroy函数,所以在使用指针方面收益甚微。我甚至声称使用结构而不是指针建议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); 
} 
+0

我完全同意你的看法。我只是认为我可以避免许多堆分配,因为库用于与数据库进行交互,我可以想象有很多调用创建/销毁,从而创建大量小内存分配/释放调用。但最终,我还希望API能够安全可用。 – Max

+1

如果您稍后发现这些对象的分配将成为瓶颈,那么可以通过使用不同的分配方案(如使用“wrapper_create”内的一个池)优化大部分成本。 –

+0

非常好的见解,我同意。我现在将构建简单的版本,看看它是否真的产生了问题。非常感谢迈克尔,有时仅仅在一个项目上工作,这让我们难以坚持某个想法。 – Max

1

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

的解决方案是一个指针,指向C++类,而不是实际的类。由于指针的大小相同,因此即使在C++没有任何知识的情况下,它也可以在C客户端中使用。

因此,在报头

typedef struct Hazelcast_Data_t { 
     void *data 
} Hazelcast_Data_t 

而在C++文件可以使用static_cast通过该指针来访问C++类。

+0

这与传递指向原始不完整类型的指针没有区别。 –

0

做,仅仅包含数组大,对齐到足以容纳你的C++类型的包装结构。放置 - 新增C++类型。

你可能会建立一个小型的C++的可执行文件,会生成具有SIZEOF_HAZELCAST_T和ALIGNOF_HAZELCAST_T C头文件适当界定。

+0

我不确定这种方法,即使我想这样做,但是你能否解释一些关于'ou可能需要构建一个小的C++可执行文件,它可以生成一个带有SIZEOF_HAZELCAST_T和ALIGNOF_HAZELCAST_T的C头文件defined.'这样的可执行文件将如何?每个类看起来相对容易,但我真的不知道如何获得它的“大小”https://github.com/hazelcast/hazelcast-cpp-client/blob/master/hazelcast/include/hazelcast/client/序列化/ pimpl/Data.h – Max

+0

但无论如何,正如Michael Anderson所指出的那样,我需要一个破坏函数,否则CPP类的破坏者将永远不会被调用。 – Max

+1

那么如果你的类需要析构函数,那么你可能会忘记这个想法,因为用户将不得不以这种或那种方式调用清理函数。 –