2012-03-16 68 views
1

給予代碼:複製構造函數:當存儲空間被釋放時?

class Sample 
{ 
public: 
     int *ptr; 
     Sample(int i) 
     { 
     ptr = new int(i); 
     } 
     ~Sample() 
     { 
     delete ptr; 
     } 
     void PrintVal() 
     { 
     cout << "The value is " << *ptr; 
     } 
}; 
void SomeFunc(Sample x) 
{ 
    cout << "Say i am in someFunc " << endl; 
} 
int main() 
{ 
    Sample s1= 10; 
    SomeFunc(s1); 
    s1.PrintVal(); 
} 

輸出是:

Say i am in someFunc 
Null pointer assignment(Run-time error) 

我無法理解爲什麼輸出的第二行來。輸出的第二行。我認爲編譯器在未明確指定時提供了一個拷貝構造函數。因此,在函數SomeFunc(Sample x)中,應該創建並銷燬樣本類型爲X的SomeFunc()的本地對象,並且main()中的Sample類型對象(s1)應該保持不變,只應在主要出口。請回答爲什麼上述行爲正在發生?

回答

6

爲什麼上述行爲正在發生?

答案很簡單:
因爲你沒有關注的Rule of Three

龍答:
類中是否有在析構函數的構造和釋放動態內存分配的指針成員ptr,而你的代碼創建該對象的臨時副本,同時傳遞給函數SomeFunc()通過調用拷貝構造函數,隱式生成由編譯器創建,該編譯器創建指針成員的一個shallow copy。一旦臨時函數在函數調用結束時被銷燬,內存將在析構函數中釋放,並且您將留下一個懸掛指針。當您調用函數PrintVal()導致未定義行爲時,將進一步解除此無效指針,該行爲表現在分段故障的形式。

如何避免此問題?

答案很簡單:
遵循三個規則。

龍答:
您應提供創建指針成員ptr的深層複製,拷貝構造函數。這可確保在成員中創建的對象的指針成員在程序的整個生命週期內保持有效。

編輯:
事實上,甚至可能會出現問題的函數被調用,甚至之前,特別是當你撥打:

Sample s1= 10; 

這將調用轉換構造,

Sample(int i) 

到創建一個臨時對象Sample,然後通過調用隱式拷貝構造函數來構造s1對象,如果是這種情況,在創建s1確實使指針成員ptr處於懸掛狀態之後,創建的臨時對象將被銷燬。

但是,大多數編譯器將使用Return Value Optimization(RVO)從而消除調用拷貝構造函數&因此這可能不是一個問題,需要通過應用複製省略優化。

在這兩種情況下,問題的解決方案都保持不變。

+0

非常感謝你給出這樣的答案! :) – Abhay 2012-03-16 05:53:24

+0

+1爲編輯提及轉換構造函數的情況,雖然這不是由於RVO – Sanish 2012-03-16 06:46:02

+0

@Als導致的原因:我今天創建了自己的Stack Exchange賬戶,我很驚訝答案的質量和迴應迅速。 :) – Abhay 2012-03-16 13:27:41