2016-02-20 37 views
1

在測試我的C++ 11代碼時,我遇到了一個錯誤,它的起源並不明確。我已經設法在下面的設計程序中重現錯誤。爲什麼從方法返回時本地變量中的數據損壞?

#include <iostream> 

class Vector { 
    public: 
     double* data; 
     Vector(int n) {data = new double[n];}; 
     ~Vector() {delete[] data;} 
}; 

Vector someMethod() { 
    if (true) { 
     Vector mat {3}; 
     if (true) { 
      mat.data[0] = 0.1; 
      mat.data[1] = 0.2; 
      mat.data[2] = 0.3; 
     } 
     return mat; 
    } 
} 

int main() { 
    Vector mat { someMethod() }; 
    std::cout << mat.data[0] << std::endl; 
    std::cout << mat.data[1] << std::endl; 
    std::cout << mat.data[2] << std::endl; 
    return 0; 

} 

以下輸出是由程序產生:

0 
0.2 
0.3 
*** Error in `./testy': double free or corruption (fasttop): 0x0000000001f75010 *** 
Aborted (core dumped) 

而輸出應爲:

0.1 
0.2 
0.3 

看來,所述第一值被破壞。我試用了不同的Vector長度,總是隻有第一個值被損壞。我沒有對上述行爲提出令人滿意的解釋。我懷疑這是因爲Vector對象是在if()語句處於someMethod()的塊範圍內的語句塊範圍內聲明和初始化的。但我不明白爲什麼這應該是一個問題,如果的確如此。


編輯

這兩種解決方案提出,先上後下的3/5法則和0秒以下的規則,工作。謝謝!不過,我仍然對這種行爲發生的原因感到困惑。什麼機制導致該值被破壞?當我在主方法的開始定義Vector對象時調用默認移動構造函數時,默認實現會導致此行爲?

+2

尋找所謂的「三定律」。你的代碼違反了這個基本的C++原則。 –

+2

@UlrichEckhardt這是C++ 11中的_five_的規則(或規則)(在C++ 98中是三個)。在大多數情況下,零規則也是可取的。 – emlai

+0

同意@zenith對兩個陳述。 –

回答

2

someMethod產生Vector matVector複製到main內部的Vector mat。由於您沒有指定複製構造函數,編譯器會爲您提供默認的複製構造函數,它只複製Vector所具有的每個元素,即double * data。複製後Vector都有一個指針到相同的數據。然後someMethod內部的原始mat被銷燬,意味着它的析構函數運行,這將刪除這兩個向量的數據。一旦someMethod返回您將得到Vector mat與一個無效的data指針。然後打印出無效的內存,導致未定義的行爲。在這種特殊情況下,未定義的行爲決定會給你提供一些錯誤的輸出,但它可能同樣容易引起分段錯誤。

我希望現在很清楚爲什麼天頂衛星的修復更正了這個問題。

+0

道歉我可能在我的編輯中錯誤地提出了我的問題,我相信在主方法的開始定義Vector對象時,調用了'move'構造函數,而不是'copy'構造函數,因爲該方法返回一個臨時對象。移動構造函數的情況下,你的論證是否成立? –

+2

@TeodorNikolov是的。默認的移動構造函數只是'std :: move's所有成員。由於'double *'沒有移動構造函數,所以你會得到一個普通的副本。 – nwp

1

您需要按照rule of three/five/zero,它說

如果一個類需要一個用戶定義的析構函數,用戶定義的拷貝構造函數或用戶定義的拷貝賦值運算符,它幾乎肯定需要所有三。

在C++ 11中,由於添加了移動構造函數和移動賦值操作符,所以它是五個而不是三個。

所以,你的類應該像這樣通過添加適當的拷貝構造函數和賦值操作符:

class Vector { 
    public: 
     double* data; 
     Vector(int n) {data = new double[n];} 

     // copy constructor 
     Vector(Vector const& source) { 
      data = new double[ /* the same `n` as used in to allocate *source.data */ ]; 
      // copy *data from `source` to this->data 
     } 

     // copy assignment operator 
     Vector& operator=(Vector const& source) { 
      delete[] data; 
      data = new double[ /* the same `n` as used in to allocate *source.data */ ]; 
      // copy *data from `source` to this->data 
      return *this; 
     } 

     ~Vector() {delete[] data;} 
}; 

您可以添加移動運營商做出,如果你希望它更有效率。


總結的零規則:

類具有自定義析構函數,複製/移動構造函數或複製/移動賦值運算符應與所有權專門處理(從Single Responsibility Principle下文)。其他類不應該具有自定義析構函數,複製/移動構造函數或複製/移動賦值運算符。

它應該是這樣的:

#include <memory> 

class Vector { 
    public: 
     std::unique_ptr<double[]> data; 
     Vector(int n) {data.reset(new double[n]);} 

     // no need to implement move constructor and move assignment operator, 
     // the automatically generated ones do the Right Thing because 
     // the resource is managed by the unique_ptr 

     // copy constructor and copy assignment operator are deleted 
     // because unique_ptr enforces the uniqueness of its managed 
     // resource 

     // if we wanted, we could implement the copy operators to do 
     // a deep copy of `data`, then we would just need to declare 
     // the move operators `= default` to not have them deleted. 

     // no need for destructor: unique_ptr manages the resource 
}; 
+0

通過顯式實現複製構造函數和複製分配來定義Vector類,提供了正確的行爲。然而,我在調用'move'構造函數來定義Vector對象的主開始部分,但沒有明確實現,但答案是正確的。這是否表明即使'someMethod()'返回的對象是臨時對象,也會調用'copy'構造函數? –

相關問題