2014-03-04 31 views
4

所以我讀了Developing C wrapper API for Object-Oriented C++ code,我喜歡這種方法,我用我的庫爲每個相應的C++類使用了不透明的句柄;避免使用void*C++庫的C封裝 - 繼承是什麼?

但是現在,我正在考慮'接口'和基類。例如,我有一個「通道」類的類層次結構 - 「通道」的基類和派生的具體類,例如,串行通信,內存緩衝區,套接字等。

所以我有:

typedef struct serial_channel serial_channel; 
typedef struct socket_channel socket_channel; 
typedef struct memory_channel memory_channel; 

serial_channel* create_serial_channel(); 
socket_channel* create_socket_channel(); 
memory_channel* create_memory_channel(); 

但我希望能夠對這些中的任何一個通到一個函數將其與「設備」對象相關聯:

void associate_device_with_channel(device*, channel*); 

易於在C++中,因爲它理解基類。我如何在C包裝庫中解決這個問題 - 在C中是什麼類型的channel

我能想到的唯一事情就是我必須求助於void *來表示基類嗎?

typedef void* channel; 
void associate_device_with_channel(device*, channel*); 

它的工作原理,但將讓我過去任何指針?

在另一個極端,我可以寫匹配衍生通道類的一組功能:

void associate_device_with_serial_channel(device*, serial_channel*); 
void associate_device_with_socket_channel(device*, socket_channel*); 
void associate_device_with_memory_channel(device*, memory_channel*); 

這是非常詳細的,如果我要添加新的渠道類型,我有新的功能添加到界面也是如此。

是否有某種中間層我失蹤了? - 像一個單一的功能,但不是void *?

+1

而不是多個通道類型,請使用單一類型。當且僅當您需要提供僅適用於特定類型的頻道的專用功能時,才包含類型代碼。在這種情況下,類型檢查必須在運行時執行。 –

+1

當您將基類(結構體)包含爲繼承類(結構體)的第一個元素時,您可以在C中實現繼承效果,利用指向結構對象的指針指向其初始成員的事實。檢查這個答案的例子http://stackoverflow.com/a/415536​​/2549281 – Dabo

+2

@Dabo他不想在C中繼承。他試圖通過不透明的C句柄公開他的C++繼承結構。 – Sneftel

回答

3

沒有任何完美的方法。你試圖讓你的函數採取一些不透明的句柄(具有適當基類的句柄),但不是任何句柄類型(其中void*會接受),並且C中沒有這個東西。

如果你喜歡,你可以提供一個功能,它需要serial_channel*並且返回channel*,另一個爲另一個channel子類。這可以讓你遠離不安全的C鑄造,並且不需要numfuncs*numderivedclasses不同的channel -taking功能。

就我個人而言,我只是void*而已。畢竟,他們使用C語言......顯然他們並不在乎他們的語言保持安全。

1

如果您只定位GCC或Clang(如果您針對的是Visual Studio,我懷疑您不打擾C),您的選擇之一是使用非標準__transparent_union__屬性創建一個聯合以列出類型一個函數可以接受。接受具有__transparent_union__屬性的聯合參數的函數將接受該聯合或其中包含的任何類型。

union associable_channel 
{ 
    channel* a; 
    serial_channel* b; 
    socket_channel* c; 
    memory_channel* d; 
} __attribute__((__transparent_union__)); 

void associate_device_with_channel(union associable_channel chan); 

serial_channel* serial; 
socket_channel* socket; 
memory_channel* mem; 
associate_device_with_channel(serial); 
associate_device_with_channel(socket); 
associate_device_with_channel(mem); 
+1

這是一個很酷的功能,但我會在這裏擔心。 '__transparent_union__'在放入'serial_channel'並取出'channel'時不會執行任何指針調整。所以在存在多重繼承的情況下,這可能會悄無聲息地搞砸了。 – Sneftel

+0

有趣的功能,在概念上與每個成員都有一個非顯式聯合構造函數相同嗎? – Trillian

+0

它只適用於功能參數。例如,你不能'union associable_channel foo = serial'。 (好吧,我們正在討論一個非標準的特性,很明顯一個供應商可以做到這一點,但是這種行爲並沒有在GCC或Clang中指定。) – zneak

2

首先,我建立了我的結構是這樣的:

typedef void base_class; 
struct base_class_impl 
{ 
    // base class member variables go here 
} 
struct derived_class 
{ 
    // base class must come first in the derived struct 
    struct base_class_impl base; 
    // derived class member variables go here 
} 

然後,我會採取指針base_class作爲參數傳遞給我的功能:

int base_class_get_count(base_class *b); 

,我會始終在該功能的開始處施放:

int base_class_get_count(base_class *b) 
{ 
    struct base_class *base = (struct base_class *)b; 
    // Operate on the object now 
} 

這使base_class_get_count()即使在派生類型的對象上也能工作。缺點是它不允許派生類型重寫一個方法 - 你必須更進一步,實現API調用的函數指針表(如base_class_get_count)分派給的基礎上的條目桌子。

+0

謝謝克里斯。調度不是問題,因爲它是幕後的所有C++。最終,你的基類仍然是void *。我猜這是有用的,以顯示意圖並創建一個代表基類的typedef,即使它是void *。 –

+1

你是對的,它仍然是一個無效*。但是,如果你確實希望基類方法接受派生類型的對象,我相信這是唯一的方式在C中。否則,你必須有一個「神」結構,其中包含所有的成員變量派生類(如果它們包含額外的成員變量)並通過這種類型 - 這有點破壞了面向對象開始的目的。然後void *方法讓你打開意外傳入錯誤對象的情況 - 編譯器不會幫助你。我認爲這是一個可以接受的折衷。 –

+1

是的,引用Sneftel的另一個答案 - 「他們使用C,畢竟......顯然他們不關心他們的語言太多,保持他們的安全。」 :-) –