2017-06-21 117 views
1

假設我有一個名爲PointerSet<T>的類。它本質上是一個vector,通過使用std::lower_bound就像set一樣工作(但它的工作原理並不重要,所以我不會詳細討論)。封裝:公共成員vs公共方法

template<typename T> 
class PointerSet 
{ 
    ... 

public: 
    void insert(T&); 
    void erase(T&); 
    void erase(const std::vector<T>&); 
}; 

將有一個「功能隧道」擊中表現? (我不知道技術術語,但這是我腦海中所稱的,它是一個函數,具有完全相同的目的。)請參閱版本1

版本1:創建成員方法

... 

template<typename T> 
class Node 
{ 
    PointerSet<T> Links; 
public: 
    void insertLink(T& p){ Links.insert(p); } 
    void eraseLink(T& p){ Links.erase(p); } 
    void eraseLink(const std::vector<T>& p){ Links.erase(p); } 
}; 

class bar; 
class foo : public Node<bar> { }; // now foo can insert links to bars. 

... 

第2版:只需使用一個公共成員

... 

class bar; 
struct foo 
{ 
    PointerSet<bar> Links; // Use Links directly 
}; 

哪一個是最好的方法?我正在考慮性能調試的方便性。

+0

那麼,如果這個實現是一個''vector'',就像一個'set'一樣,那麼你的公共接口將會很容易*錯誤地使用你的'PointerSet'類。在矢量中分配很多項目並要求對它們進行排序對於大數據來說效率非常低,因爲在插入或刪除項目時,您總是會複製大量數據。一種可能性是實現堆。除非您有大量小項目,並且要麼執行堆,要麼確保數據只在數據加載後被排序(並且不刪除項目),那麼如果您遇到性能問題,這可能是有意義的。 – Phil1970

+0

@ Phil1970我也這麼認爲,但我在實際性能測試中發現,即使在一些非常大的數據上,vector也仍然優於set。無論如何,直到數據變得天文數字爲止。我猜移動語義幫助向量在切換元素時非常快速。也許你或某人可以澄清這一點?不過,我認爲這是一個不同的問題。 –

+0

那麼,如果性能真的很重要,那麼你應該測量它,所以這個問題是毫無意義的,因爲你會知道真正的影響你的實際數據和用法......否則,你可以假設'std :: set'是適當而不是重新發明輪子。另外,您必須確保您以最糟糕的情況進行測試。一個矢量可以更快達到給定大小(取決於項目大小及其複製/移動實現)以及實際硬件(高速緩存大小...),主要是使用模式(插入/刪除與搜索次數的比率)。 – Phil1970

回答

3

威爾具有「功能通道」採取一擊,以性能?

最有可能不是。特別是因爲你在這裏處理模板,所以定義將全部可見並且易於編譯器內聯。看看這個鏈接:https://godbolt.org/g/cWR7N3

我所做的是編譯這兩個代碼片段。首先,調用Node類中的函數。

#include <vector> 

// these functions are not defined, so that the compiler 
// cannot inline them or optimize them out 
void insert_impl(void const*); 
void erase_impl(void const*); 
void erase_impl_vec(void const*); 

template<typename T> 
class PointerSet 
{ 
public: 
    void insert(T& v) { insert_impl(&v); } 
    void erase(T& v) { erase_impl(&v); } 
    void erase(const std::vector<T>& v) { 
     erase_impl_vec(&v); 
    } 
}; 

template<typename T> 
class Node 
{ 
    PointerSet<T> Links; 
public: 
    void insertLink(T& p){ Links.insert(p); } 
    void eraseLink(T& p){ Links.erase(p); } 
    void eraseLink(const std::vector<T>& p){ Links.erase(p); } 
}; 

int main() 
{ 
    Node<int> n; 

    int x; 
    n.insertLink(x); 
    n.eraseLink(x); 

    std::vector<int> v; 
    n.eraseLink(v); 
} 

然後直接從PointerSet類中調用它們。

#include <vector> 

// these functions are not defined, so that the compiler 
// cannot inline them or optimize them out 
void insert_impl(void const*); 
void erase_impl(void const*); 
void erase_impl_vec(void const*); 

template<typename T> 
class PointerSet 
{ 
public: 
    void insert(T& v) { insert_impl(&v); } 
    void erase(T& v) { erase_impl(&v); } 
    void erase(const std::vector<T>& v) { 
     erase_impl_vec(&v); 
    } 
}; 

int main() 
{ 
    PointerSet<int> n; 

    int x; 
    n.insert(x); 
    n.erase(x); 

    std::vector<int> v; 
    n.erase(v); 
} 

正如您在鏈接(https://godbolt.org/g/cWR7N3)中看到的,編譯器會爲每個輸出輸出相同的程序集。

main:         # @main 
     push rbx 
     sub  rsp, 48 
     lea  rbx, [rsp + 12] 
     mov  rdi, rbx 
     call insert_impl(void const*) 
     mov  rdi, rbx 
     call erase_impl(void const*) 
     xorps xmm0, xmm0 
     movaps xmmword ptr [rsp + 16], xmm0 
     mov  qword ptr [rsp + 32], 0 
     lea  rdi, [rsp + 16] 
     call erase_impl_vec(void const*) 
     mov  rdi, qword ptr [rsp + 16] 
     test rdi, rdi 
     je  .LBB0_3 
     call operator delete(void*) 
.LBB0_3: 
     xor  eax, eax 
     add  rsp, 48 
     pop  rbx 
     ret 
     mov  rbx, rax 
     mov  rdi, qword ptr [rsp + 16] 
     test rdi, rdi 
     je  .LBB0_6 
     call operator delete(void*) 
.LBB0_6: 
     mov  rdi, rbx 
     call _Unwind_Resume 
GCC_except_table0: 
     .byte 255      # @LPStart Encoding = omit 
     .byte 3      # @TType Encoding = udata4 
     .byte 41      # @TType base offset 
     .byte 3      # Call site Encoding = udata4 
     .byte 39      # Call site table length 
     .long .Lfunc_begin0-.Lfunc_begin0 # >> Call Site 1 << 
     .long .Ltmp0-.Lfunc_begin0 # Call between .Lfunc_begin0 and .Ltmp0 
     .long 0      #  has no landing pad 
     .byte 0      # On action: cleanup 
     .long .Ltmp0-.Lfunc_begin0 # >> Call Site 2 << 
     .long .Ltmp1-.Ltmp0   # Call between .Ltmp0 and .Ltmp1 
     .long .Ltmp2-.Lfunc_begin0 #  jumps to .Ltmp2 
     .byte 0      # On action: cleanup 
     .long .Ltmp1-.Lfunc_begin0 # >> Call Site 3 << 
     .long .Lfunc_end0-.Ltmp1  # Call between .Ltmp1 and .Lfunc_end0 
     .long 0      #  has no landing pad 
     .byte 0      # On action: cleanup 
1

除非您想限制可以在該數據成員上執行的操作,否則我會建議使用公共成員。如果你只是用自己的方法名稱來包裝方法,那麼把它們放在那裏是沒有意義的。

+0

那麼它真的取決於'foo'的用途類。如果它只包含幾個變量,那麼公共成員可能就足夠了。否則,最好寫一些額外的內聯函數,因爲如果實現改變,它可以使維護更容易。 – Phil1970

0
  • 一般來說,如果IS-A關係不被尊重,您應該避免公開推導,因爲它會導致更高的耦合度。
  • 您還應該避免使用公共成員,因爲它可能會使應用程序在將來難以維護。
  • 如果您可以直接在您的第二個樣本中使用PointerSet,那麼在版本1中,您也可以直接從PointerSet派生。因此比較是不公平的。

實際上,可接受的折衷取決於每個班級的實際用途。額外的間接性是無用的,如果他們不給任何益處(減少編譯依賴,接入/控制的可視性,額外的功能,代碼重用的機會...)