2012-05-19 213 views
6

考慮這段代碼:拷貝構造

#include <vector> 
#include <iostream> 
using namespace std; 

class Base 
{ 
    char _type; 
public: 
    Base(char type): 
     _type(type) 
    {} 

    ~Base() { 
     cout << "Base destructor: " << _type << endl; 
    } 
}; 

class uncopyable 
{ 
    protected: 
     uncopyable() {} 
     ~uncopyable() {} 
    private: 
     uncopyable(const uncopyable&); 
     const uncopyable& operator=(const uncopyable&); 
}; 

class Child : public Base, private uncopyable 
{ 
    int j; 
public: 
    Child(): 
     Base('c') 
    {} 
    ~Child() { 
     cout << "Child destructor" << endl; 
    } 
}; 


int main() 
{ 
    vector<Base> v; 
    Base b('b'); 
    Child c; 

    v.push_back(b); 
    v.push_back(c); 
    return 0; 
} 

我的系統上的輸出是:

Base destructor: b 
Child destructor 
Base destructor: c 
Base destructor: b 
Base destructor: b 
Base destructor: c 

我的問題是:

  • 爲什麼是Base(類型b)的析構函數被調用三次而不是兩次(我們是否有超過兩個對象b的副本)?

  • 當我們複製一個類型爲Child的對象時,考慮到其父類的副本構造函數是私有的,會發生什麼情況。它是不確定的行爲?

  • 無論何時我試圖複製Child類型的對象,我都希望得到編譯時錯誤。我認爲孩子的默認拷貝構造函數會嘗試調用Uncopyable類的私有拷貝構造函數並導致編譯錯誤。爲什麼它不給編譯錯誤?

代碼被這樣設計的原因是因爲Child類是巨大的。

當客戶端試圖複製一個Child對象(調用Child的析構函數而不調用Base的析構函數)時,所需的行爲是拋棄子數據。

這段代碼實現了這一點,但我想它會導致未定義的行爲並且有內存泄漏(從未調用複製實例的Child的析構函數)。

+0

你想要一個'編譯時'錯誤還是你想扔掉孩子的數據?這些是相互矛盾的陳述,除非我錯過了一些東西。 – Chip

+0

@Chip修復它。謝謝。 –

回答

9

這裏是你的代碼會發生什麼:

int main() 
{ 
    vector<Base> v; // 1 
    Base b('b');  // 2 
    Child c;   // 3 

    v.push_back(b); // 4 
    v.push_back(c); // 5 
    return 0; 
}      // 6 
  1. 線1:向量v構建

  2. 線2:鹼B構成的(調用Base的構造函數)

  3. 線3:構建Child c(調用Child的構造函數和Base的構造函數)

  4. 第4行:v是最大容量時的電流,需要調整大小。
    內存分配給基地的一個元素由v。
    基地b複製到v [0](調用基地的複製構造函數)。

  5. 第5行:v再次處於最大容量並需要調整大小。
    內存分配給基地的2個元素由v。
    舊的v [0]被複制到新的v [0](調用Base的複製構造函數)。
    舊的v [0]被刪除(調用Base的析構函數(「Base destructor:b」))。
    子c被複制到v [1]中(調用Base的複製構造函數)。

  6. 第6行:c,b和v超出範圍。
    Child C級被刪除(主叫兒童的析構函數( 「兒童析構函數」),那麼基地的析構函數( 「基本析構函數:C」。)
    鹼B被刪除的(調用Base的析構函數( 「基本析構函數:B」))
    Base v [0],v [1]被刪除(調用Base的析構函數兩次(「Base destructor:b」,「Base destructor:c」))。

沒有內存泄漏 - 對於上述序列中的每個構造函數,調用相應的析構函數。

此外,你似乎對複製構造函數非常困惑。子c被傳遞給push_back作爲基地& - 然後按預期調用Base的複製構造函數。由於Base的隱式拷貝構造函數不是虛擬的或被覆蓋的,因此Child從不可拷貝派生而來並不會改變它。

請注意,vector<Base>永遠不能存儲Child類型的對象;它只知道爲Base分配足夠的內存。將一個Child實例分配給一個Base時出現的情況稱爲分割,雖然這種分割通常是無意識和誤解的,但它似乎可能實際上是您所描述場景中的所需。

+0

謝謝。我也發現這有用:http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c –

0

編輯:答案是錯誤的..當前編輯到更好的響應。

爲什麼基地(與類型b)的析構函數調用三次而不是兩次(我們有超過兩個對象b的副本)?

最有可能的是,載體是製作b副本。矢量經常這樣做。

當我們複製Child類型的對象時,考慮到其父類之一的副本構造函數是私有的,會發生什麼。它是不確定的行爲?

否.C的複製構造函數將調用基類的複製構造函數。所以如果基類拷貝構造函數是私有的,它將不會被編譯。

無論何時我嘗試複製類型爲Child的對象,並允許複製基類對象時,我都需要獲取編譯時錯誤。什麼是最好的方式來做到這一點?

聲明的私人拷貝構造函數的孩子,就像這樣:

private: 
    Child(const Child& a) { 
     throw "cannot make a copy"; 
    } 

要求的性能扔掉每當客戶端嘗試複製的子對象的子數據(調用兒童的析構函數而不需要調用Base的析構函數)。

不確定你的意思。複製構造函數意味着創建一個新對象。您無法對(舊)對象執行操作。

+0

1.恕我直言,應該複製一次。不是兩次。 2.恕我直言孩子的隱式複製構造函數應該調用父級的複製構造函數。 http://stackoverflow.com/questions/9178204/why-does-the-implicit-copy-constructor-calls-the-base-class-copy-constructor-and 3.它不工作(你可以試試它)。因爲該對象首先被輸入到父級,然後被複制。 –

+0

在建議的代碼中,我們正在創建一個新的Child,而不是複製數據。它擊敗了不再創造新對象的目的。 –

+0

我想我錯過了一些東西。從另一個對象創建對象時,將調用複製構造函數。這就是複製構造函數的作用...你能解釋你想做什麼嗎? – Chip

3

每當我嘗試複製Child類型的對象時,我都希望得到編譯時錯誤。

您並未複製Child對象。當您將Childc設置爲vector<Base>時,只有Base被複制。這與執行b = c;基本相同。如果您複製/分配Child,則會出現錯誤。

Child d = c; // compile error 

默認的拷貝構造函數會調用任何基類和成員對象的拷貝構造函數和原語和指針做一個按位複製。

+0

謝謝。這似乎是正確的。那麼額外調用Base的析構函數呢? –

+1

@Shayan,它可能是一個額外的副本向量<>,而重新分配其存儲。不同的vector <> impls可能在分配時表現不同。 – xan