2012-09-12 172 views
45

在我工作的地方我看到這種風格廣泛使用: -參考成員變量的類成員

#include <iostream> 

using namespace std; 

class A 
{ 
public: 
    A(int& thing) : m_thing(thing) {} 
    void printit() { cout << m_thing << endl; } 

protected: 
    const int& m_thing; //usually would be more complex object 
}; 


int main(int argc, char* argv[]) 
{ 
    int myint = 5; 
    A myA(myint); 
    myA.printit(); 
    return 0; 
} 

是否有來形容這個成語的名稱?我假設它是爲了防止複製大型複雜對象的可能的大開銷?

這是一個很好的做法嗎?這種方法有沒有陷阱?

+2

如果您在成員變量中引用的對象在別處銷燬並嘗試通過類訪問它,則可能出現的一個錯誤 – mathematician1975

回答

60

有沒有一個名稱來形容這個成語?

在UML中稱爲聚合。它不同於成分,因爲成員對象不是所屬的。在C++中,可以通過引用或指針以兩種不同的方式實現聚合。

我假設它是爲了防止複製大型複雜對象的可能大開銷?

不,這將是一個非常糟糕的理由來使用它。聚合的主要原因是包含的對象不屬於包含對象,因此它們的生命週期不受限制。特別是引用的對象的生命週期必須超過引用的。它可能早已創建,可能會超出容器使用期限的壽命。除此之外,引用對象的狀態不受類的控制,但可以在外部進行更改。如果引用不是const,那麼該類可以更改處於其外部的對象的狀態。

這是一般的良好做法嗎?這種方法有沒有陷阱?

這是一個設計工具。在某些情況下,這將是一個好主意,有些則不會。最常見的錯誤是持有引用的對象的生命週期不得超過引用對象的生命週期。如果封閉對象在之後使用引用引用的對象被銷燬,您將會有未定義的行爲。一般來說,選擇合成比較好,但如果你需要它,它就和其他工具一樣好。

+1

「不,這將是一個非常糟糕的理由使用這個」。您能否詳細說明這一點?有什麼可以用來實現呢? – coincoin

+0

@coincoin:爲了達到目的? –

+1

'防止可能大的開銷複製大複雜對象的?' – coincoin

1

成員引用通常被認爲是不好的。與成員指針相比,它們讓生活變得艱難。但它不是特別不真實,也不是一些特殊的命名成語或事物。這只是別名。

+13

您能否提供對支持的引用*通常被認爲是不好的*? –

1

C++提供了一個很好的機制來管理對象的生命期,儘管通過類/結構的構造。這是C++比其他語言最好的特性之一。

當你通過ref或pointer暴露成員變量時,它原則上違反了封裝。這個習慣用法使得階級的消費者能夠在沒有它的情況下改變A的對象的狀態(A)對它有任何的知識或控制。它還使消費者能夠在A的對象的生命週期之後保持對A的內部狀態的引用/指針。這是糟糕的設計。相反,這個類可以被重構爲持有一個ref /指向共享對象(不擁有它),這些可以使用構造函數(命令生命時間規則)來設置。視情況而定,共享對象的類可能被設計爲支持多線程/併發。

+0

但OP中的代碼不包含任何成員變量的引用。它有一個參考成員變量。所以,偉大的觀點,但看似無關。 –

16

有沒有一個名字來形容這個習語呢?

有這個用法沒有名字,它被簡稱爲「作爲參考類成員」

我假設它是爲了防止複製大型複雜對象的可能的大開銷?

是的,以及您想要將一個對象的生命週期與另一個對象關聯的場景。

這是一般的良好做法嗎?這種方法有沒有陷阱?

取決於您的使用情況。使用任何語言功能就像「選擇課程的馬」。需要注意的是,每個(幾乎所有)語言功能都存在,因爲它在某些情況下很有用。
有幾個重要的點,利用引用爲類成員時的注意事項:

  • 您需要確保引用的對象是保證生存,直到你的類對象存在。
  • 您需要在構造函數成員初始值設定項列表中初始化成員。你不能有一個延遲初始化,這可能在指針成員的情況下。
  • 編譯器不會生成複製分配operator=(),您將不得不自己提供一個。確定運營商在這種情況下應該採取什麼行動是很麻煩的。所以基本上你的班級變成不可轉讓
  • 參考文獻不能是NULL或作爲引用任何其他對象。如果您需要重新安裝,那麼在指針的情況下不可能使用引用。

對於大多數實際目的(除非您真的擔心由於成員大小導致的高內存使用率),只有成員實例,而不是指針或引用成員就足夠了。這可以節省您很多擔心引用/指針成員帶來的其他問題,但會犧牲額外的內存使用量。

如果您必須使用指針,請確保使用智能指針而不是原始指針。用指針可以讓你的生活變得更容易。

+0

「**我假設它是爲了防止複製大型複雜對象的可能的大開銷**是的 - 請詳細說明爲什麼您認爲此模式與避免複製有任何關聯,因爲我看不到任何複製對象。如果這個非'idiom'以某種方式將任何人的副本保存爲其真實目的的副作用,那麼他們的原始設計就會有致命的缺陷,只是用這種模式替換原來的設計不太可能達到他們期望的效果。 –

19

它被稱爲dependency injection via constructor injection:類A獲取依賴作爲其構造函數的參數,並將對依賴類的引用保存爲私有變量。

wikipedia有一個有趣的介紹。

對於常量,正確性我會寫:

using T = int; 

class A 
{ 
public: 
    A(const T &thing) : m_thing(thing) {} 
    // ... 

private: 
    const T &m_thing; 
}; 

但這個類的一個問題是,它接受臨時對象的引用:

T t; 
A a1{t}; // this is ok, but... 

A a2{T()}; // ... this is BAD. 

這是更好地添加(需要C++ 11至少):

class A 
{ 
public: 
    A(const T &thing) : m_thing(thing) {} 
    A(const T &&) = delete; // prevents rvalue binding 
    // ... 

private: 
    const T &m_thing; 
}; 

無論如何,如果你改變了構造函數:

class A 
{ 
public: 
    A(const T *thing) : m_thing(*thing) { assert(thing); } 
    // ... 

private: 
    const T &m_thing; 
}; 

這幾乎是保證you won't have a pointer to a temporary

而且,由於構造函數需要一個指針,它更清晰到 A用戶,他們需要注意他們傳遞對象的生命週期。


有點相關的主題有: