堆棧

2016-07-12 154 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

那麼如果你的類需要析構函數,那麼你可能會忘記這個想法,因爲用戶將不得不以這種或那種方式調用清理函數。 –