2012-10-10 67 views
0

可能重複:
What is The Rule of Three?在此示例中是否爲重載運算符=必需的?

人們說,如果你需要一個析構函數,那麼你確實需要一個重載operator=

struct longlife{ }; 
class z 
{ 
z(){}; 
~z(){ for(auto it=hold.begin();it!=hold.end() ++it) delete(*it); }; 
vector<longlife*> hold; 
}; 

假設插在hold所有指針都new堆分配,除了decon之外,爲什麼還有其他東西這個例子需要structor嗎?

通過anything else我的意思是一樣的東西,

z& operator=(const z&ref) 
{ 
hold = ref.hold; 
return *this; 
} 

請問:

z a; 
a.hold.push_back(heap_item); 
z a2; 
a2 = a; 

導致內存泄漏?有時很難理解爲什麼三條規則是規則

+5

關於「三法則」的第一條規則是您應該遵守[Rule of Zero](http://rmartinho.github.com/2012/08/15/rule-of-zero.html)。 –

+0

@Xeo,我明白三條規則是什麼,問題大多是爲什麼它是一條**規則** –

+4

如果你問那個,那你就不明白了。 – Xeo

回答

3

不僅需要賦值運算符,還需要實現複製構造函數。否則,編譯器將提供默認實現,這將導致兩個副本(分配/複製構造後)包含指向相同longlife實例的指針。這兩個副本的破壞者然後將delete這些實例導致未定義的行爲。

z a; 
a.hold.push_back(heap_item); 
z a2; 
a2 = a; 

兩個a.hold[0]a2.hold[0]包含一個指針,指向同一heap_item;從而在銷燬期間造成雙重刪除。

避免必須實現賦值運算符和複製構造函數的簡單方法是使用智能指針將longlife實例保存在vector中。

std::vector<std::unique_ptr<longlife>> hold; 

現在沒有必要爲您的班級編寫析構函數。


對於C++ 03,你的選擇是使用std::tr1::shared_ptr(或boost::shared_ptr),而不是unique_ptr或使用boost::ptr_vector(當然,這也是C++ 11的選項),而不是std::vector

+0

如何在C++ 03上使用'unique_ptr'? –

+1

@ViniyoShouta更新了答案。 – Praetorian

+0

恐怕C++ 03也沒有shared_ptr =( –

1

實際上這裏會有一個雙重空閒的,而不是內存泄漏。

STL容器存儲對象,而不是引用。在你的情況下,object是一個指針。指針只是複製。你的行a2 = a;將複製向量中的指針。之後,每個析構函數將釋放指針。

Double free比內存泄漏要危險得多。它會導致討厭的未定義行爲:

MyStruct *p1 = new MyStruct(); 
delete p1; 
.... do something, wait, etc. 
delete p1; 

在同一時間其他線程:

MyOptherStruct *p2 = new MyOtherStruct(); 
.... do something, wait, etc. 
p2->function(); 

它可能會證明,內存分配將分配給p2正是用於p1相同的值,因爲在第一次致電delete p1後它是免費的。一段時間後第二個delete p1也將罰款,因爲分配者認爲這是一個合法的指針,發出了p2。該問題只會在p2->function();出現。看看線程2的代碼,絕對不可能理解出錯和原因。這非常難以調試,特別是如果系統很大。

2

因爲沒有賦值運算符,你可以用多個hold指向同一個堆項目,一旦破壞導致不確定的行爲向量最終拷貝構造函數:

z firstZ; 
if (somethingIsTrue) { 
    z otherZ = firstZ; 
    // play with otherZ... 
    // now otherZ gets destructed, along with longlife's of the firstZ 
} 
// now it's time to destroy the firstZ, but its longlife's are long gone! 

當然,你不會有這個問題你是否使用了一個對象矢量或「智能指針」向量,而不是「普通舊」指針向量。

查看Rule of Three瞭解更多信息。

+0

甚至更​​好,[規則零](http://rmartinho.github.com/2012/08/15/rule-of-zero.html) –

+0

@MooingDuck完全同意 - 我甚至暗示當我提到聰明的指針。 – dasblinkenlight

1

這將導致對aa2析構函數(無論被毀第二),因爲默認分配構造會做的hold內存狀態的二進制副本雙刪除(和崩潰)。因此,每個對象aa2將最終刪除完全相同的內存。

1

從您的評論:

@Xeo,我理解三者的規則是什麼,這個問題主要是 爲什麼它的規則

考慮在這裏會發生什麼:

z& operator=(const z&ref) 
{ 
hold = ref.hold; 
return *this; 
} 

假設你有一個z的實例:

z myz; 
myz.a.hold.push_back(new long_life); 

...然後你創建這個myz的副本:

z my_other_z; 
// ... 
my_other_z = myz; 

你在上面簡單地拷貝vector的內容提供的operator=實現。如果vector有指針,它不會複製指向它的任何東西 - 它只是創建指針本身的文字副本。

所以在operator=返回後,您將有兩個實例z指針指向相同的東西。當第一的z S的破壞,它會刪除指針:

~z(){ for(auto it=hold.begin();it!=hold.end() ++it) delete(*it); }; 

當談到時間爲第二z被破壞,它會嘗試delete相同的指針第二次。這導致未定義的行爲。

解決此問題的方法是在分配或複製維護需要分配和刪除的資源的對象時進行深度複製。這意味着提供一個賦值操作符和一個拷貝構造函數。

這就是爲什麼三條規則是規則。

編輯:

正如其他人所說,這是所有好使用值語義和RAII完全避免。如同其他人所稱的那樣,重新設計您的對象以使用零規則是一種更好的方法。