2015-03-31 24 views
0

我有一個庫中有多個類,它們具有我希望從客戶端代碼隱藏的內部特徵。從客戶端的角度來看,每個類都是從庫類中查詢的,並且僅用作不透明指針。一個例子如下:隱藏公共接口和ODR中的成員

struct SomeSystem; 
void doSomethingToSomeSystem(SomeSystem* system, Parameters params); 
void doSomethingElseToSomeSystem(SomeSystem* system, Parameters params); 

在實施側,SomeSystem具有多個構件,其不給調用者可見。這是所有罰款,但我真的不喜歡笨重的使用語法:

SomeSystem* system = lib->getSomeSystem(); 
doSomethingToSomeSystem(system, params); 
doSomethingElseToSomeSystem(system, params); 

另一種方法是這樣的:

struct SomeSystem; 
namespace somesystem { 
    void doSomething(SomeSystem* system, Parameters params); 
    void doSomethingElse(SomeSystem* system, Parameters params); 
} 

隨着使用代碼:

SomeSystem* system = lib->getSomeSystem(); 
somesystem::doSomething(system, params); 
somesystem::doSomethingElse(system, params); 

我可以還使用全局方法doSomethingdoSomethingElse,並且如果另一種類型也定義了doSomething,則取決於函數重載。但是,在這種情況下,很難在IDE中找到SomeSystem的所有「成員」。

我很想實際使用成員函數:

struct SomeSystem { 
    void doSomething(Parameters params); 
    void doSomethingElse(Parameters params); 
}; 

隨着使用代碼:

SomeSystem* system = lib->getSomeSystem(); 
system->doSomething(params); 
system->doSomethingElse(params); 

最後片段對我來說很好,但SomeSystem不再是一個不透明的指針 - 它實際上定義了成員。我對此有點謹慎。一個潛在的問題是一個定義規則。然而,只有不同的翻譯單位才能看到該類的「公共」定義和「私人」定義。這裏是否還有其他不良之處?如果客戶端代碼嘗試在堆棧上實例化SomeSystem或使用新的代碼,它顯然會使程序崩潰。但我願意接受這一點。也許我可以通過在公共接口中提供一個私有構造函數來解決這個問題。

另一種方法當然是用純虛擬方法定義抽象類。 但是,如果不是絕對必要的,我想避免這種開銷。

編輯:

要清楚,我想知道,如果它是合法的,有一個公共的頭,該客戶端包括包含類的不是什麼實現使用一個不同的定義(有一些成員失蹤) ,因爲客戶端從不實例化類。

公共標題:

struct SomeSystem { 
    void doSomething(Parameters params); 
    void doSomethingElse(Parameters params); 
}; 

私人標題:

struct SomeSystem { 
    Member member; 
    void doSomething(Parameters params); 
    void doSomethingElse(Parameters params); 
}; 

私人來源(包括私有頭):

void SomeSystem::doSomething(Parameters params) { 
    ... 
} 
void SomeSystem::doSomethingElse(Parameters params) { 
    ... 
} 

這時候我測試,但我不確定是否工作它以某種方式違反了標準。兩個標題永遠不會包含在同一個翻譯單元中。

+0

您是否熟悉[不透明指針/ Pimpl成語](http://en.wikipedia.org/wiki/Opaque_pointer)? – 2015-03-31 16:47:23

+0

是的,我很熟悉它,我寧願避免它。它引入了額外的內存分配並使執行復雜化。 – rasmus 2015-03-31 16:51:16

+0

請注意,我特別是在語法糖後沒有使我的解決方案性能或複雜性更差。 – rasmus 2015-03-31 16:52:55

回答

0

PIMPL習慣用法在這種情況下可能是理想的,但它對每個訪問都是一種額外的間接方式,所以就是這樣。

如果你只是經過一些語法糖可能會採取ADL的優勢的選擇 - 它至少會保持系統的名字在函數名稱:

// publicly shared header file 
namespace one_system 
{ 
    struct system; 
    typedef system* system_handle; 
    void do_something(system_handle); 
}; 

// private implementation 
namespace one_system 
{ 
    struct system {}; 
    void do_something(system_handle) { cout << "one"; } 
}; 


int main() { 
    auto handle = /* SOMETHING TO GET THIS SYSTEM */; 
    do_something(handle); //do_something found by ADL 
    return 0; 
} 

編輯

我仍然認爲PIMPL是理想的。與已有的相比,您也不一定需要分配或額外的開銷。

如果你有一個系統*和一個函數聲明(如你的例子),編譯器必須做一個間接尋址。您需要跳轉到該函數(因爲它在另一個翻譯單元中定義)以及在函數中訪問系統的間接指針(因爲它被當作指針)。

你真正需要做的是定義爲類的接口,像這樣:在另一個翻譯單元

// put this in a namespace or name it according to the system 
class interface 
{ 
    system_handle m_system; 

    public: 
    interface(system_handle s) : m_system(s) {} 
    interface() = delete; 

    void do_something(); 
}; 

現在,do_something()被定義爲執行它的系統上運行。 lib-> GetSystem()可以返回接口的一個實例。該接口可以在頭文件中完全聲明,因爲它只包含公共函數。系統仍然是完全私有的(因爲lib的用戶不會有頭文件聲明它的內容)。

此外,接口可以由用戶簡單地複製。它不關心它的指針來自哪裏,所以如果需要的話,庫可以傳入靜態地址。

我可以看到的一個缺點是成員變量需要訪問者(並且有很多人會爭辯說,每個成員變量都應該是私有的並且具有公共或受保護的訪問者)。

另一種情況是* this用於接口將被傳遞到do_something並且可能不需要。這可以通過在頭文件也已經do_something定義來解決:

void do_something() { do_something_to_system(m_system); } 

現在,編譯器應該能夠甚至優化*這一點,因爲do_something可內聯和編譯器可以很容易地只是插入代碼加載m_system寫入一個寄存器並調用do_something_to_system(這與您在示例中提到的內容類似)。

+0

我不同意。 PIMPL並不理想,因爲它像你說的那樣引入了額外的間接性。更糟糕的是,它引入了額外的內存分配並給實施帶來了更多負擔。除非您使用'namespace do_something',否則您的one_system示例將不起作用。這也正是我在我的問題中討論的。儘管感謝您的回答。 – rasmus 2015-03-31 17:10:55

+0

@rasmus它的哪部分不起作用?您不需要爲編譯器使用namespace do_something來查找函數 - 您只需要在與函數相同的名稱空間中聲明句柄或系統即可。 – qeadz 2015-03-31 17:17:02

+0

好吧,我錯了,我錯過了在命名空間內定義的系統。我仍然認爲這是一個問題,IDE將不會列出成員,除非您鍵入one_system :: though。 – rasmus 2015-03-31 17:20:15