2011-12-31 108 views
1

的代碼是:什麼時候複製構造函數和析構函數被調用,爲什麼?

#include <iostream> 

class P_Node { 
    friend class Picture; 
protected: 
    P_Node() : use(1) {} 
    virtual ~P_Node() {} 
private: 
    int use; 
}; 

class Picture { 
    friend Picture frame(const Picture&); 
public: 
    Picture() : p(new P_Node) { 
     std::cout << "Constructor\t" << "Picture::Picture()" << "\tcalled" << std::endl; 
     std::cout << "Picture p count\t" << p->use << std::endl; 
    } 
    Picture(const Picture& orig) : p(orig.p) { 
     std::cout << "Copy Constructor\t" << "Picture::Picture(const Picture&)" << "\tcalled" << std::endl; 
     std::cout << "Picture p count\t" << p->use << std::endl; 
     orig.p->use++; 
    } 
    ~Picture() { 
     std::cout << "Destructor\t" << "Picture::~Picture()" << "\tcalled" << std::endl; 
     std::cout << "Picture p count before decrease\t" << p->use << std::endl; 
     if(--p->use == 0) { 
      std::cout << "Picture p count after decrease\t" << p->use << std::endl; 
      std::cout << "Deleted" << std::endl; 
      delete p; 
     } 
    } 
    Picture& operator=(const Picture& orig) { 
     std::cout << "operator=\t" << "Picture& Picture::operator=(const Picture& orig)" << "\tcalled" << std::endl; 
     std::cout << "Picture p count before decrease\t" << p->use << std::endl; 
     orig.p->use++; 
     if(--p->use == 0) { 
      std::cout << "Picture p count after decrease\t" << p->use << std::endl; 
      std::cout << "Deleted" << std::endl; 
      delete p; 
     } 
     p = orig.p; 
     return *this; 
    } 
private: 
    Picture(P_Node* p_node) : p(p_node) { 
     std::cout << "Picture::Picture(P_Node* p_node)\tcalled" << std::endl; 
    } 
    P_Node *p; 
}; 

class Frame_Pic : public P_Node { 
    friend Picture frame(const Picture&); 
private: 
    Frame_Pic(const Picture& pic) : p(pic) { 
     std::cout << "Frame_Pic::Frame_Pic(const Picture& orig)" << "\tcalled" << std::endl; 
    } 
    Picture p; 
}; 

Picture frame(const Picture& pic) { 
    return new Frame_Pic(pic); 
} 

int main() { 
    Picture my_pic; 
    Picture temp = frame(my_pic); 
    return 0; 
} 

結果是:

 
Constructor Picture::Picture() called 
Picture p count 1 
Copy Constructor Picture::Picture(const Picture&) called 
Picture p count 1 
Frame_Pic::Frame_Pic(const Picture& orig) called 
Picture::Picture(P_Node* p_node) called 
Destructor Picture::~Picture() called 
Picture p count before decrease 1 
Picture p count after decrease 0 
Deleted 
Destructor Picture::~Picture() called 
Picture p count before decrease 2 
Destructor Picture::~Picture() called 
Picture p count before decrease 1 
Picture p count after decrease 0 
Deleted 

我以前問一個關於這個代碼的內存管理問題,但這些答案的理解後,我仍然有一個問題析構函數和複製構造函數。據我瞭解,Picture temp = frame(my_pic)將調用複製構造函數。

問題來了:

  1. 後爲什麼Picture temp = frame(my_pic)
  2. 不叫拷貝構造函數,爲什麼被稱爲析構函數?
  3. Picture frame(const Picture& pic),如果函數被調用,會調用複製構造函數嗎?我相信是這樣,因爲它會以價值回報'圖片'。
  4. 如果我將Picture frame(const Picture& pic)更改爲Picture frame(Picture p),函數調用時複製構造函數會調用兩次嗎?
  5. 何時會調用複製構造函數?當這個類被函數按值返回時會發生嗎?當然後類通過值傳遞給一個函數?
  6. 什麼時候可以調用析構函數?每次變量的生命週期結束了嗎?這是否意味着如果我通過值將一個變量傳遞給一個函數,它的析構函數將在函數執行後被調用?

我現在搞砸了複製構造函數和析構函數,特別是當我有一個帶有返回值的函數和一些參數都通過值傳遞。

此外,任何人都可以幫我寫每條輸出字符串的評論嗎?這將是非常有益的。

+3

注意,調用拷貝構造函數和賦值操作符可以自由編譯器的優化被淘汰,即使他們有副作用。這是編譯器被允許違反as-if規則的唯一兩個。 (當然,編譯器仍然需要不違反邏輯並將調用與ctors和dtors相匹配。) – sbi 2011-12-31 09:23:39

+0

@outis:否。返回表達式的類型爲'Frame_Pic *'。'Frame_Pic'派生自'P_Node','P_Node *'通過私有構造函數隱式轉換爲'Picture',這很好,因爲'frame'被聲明爲朋友。正如函數聲明的那樣,主體返回一個'Picture'。 – 2011-12-31 09:27:06

+0

@CharlesBailey所以代碼本身並不是_wrong_,但我絕對不會稱之爲好的做法。 – bames53 2011-12-31 10:07:49

回答

3

在回答你的問題。

  1. 拷貝構造函數,因爲你沒有導致該聲明之後的任何副本的任何聲明不聲明Picture temp = frame(my_pic);後調用。

  2. Picture三個析構函數被稱爲破壞(按順序):temppFrame_Pic通過temp.pmy_pic指出。您的編譯器已避免生成任何其他臨時對象Picture

  3. 是的,可能會調用一個拷貝構造函數來初始化返回值Picture frame(const Picture& pic),但編譯器被允許(並且在這種情況下)消除拷貝並直接從返回表達式初始化返回值。

  4. 是的,可如果更改frame按值傳遞,但是如果該參數與是不是指現有的對象可能的參數一個glvalue表達式初始化參數產生的額外拷貝構造函數調用直接用該表達式進行初始化,並且複製消失。

  5. 只要實際複製類類型的對象,就會調用複製構造函數。這可能是傳遞給函數或從函數返回的,但有時編譯器可以在這些場景中省略不必要的副本。

  6. 是的,只要類類型的對象被銷燬,就會調用析構函數。這對於由編譯器生成的命名變量和臨時對象來說是正確的。可以在不調用析構函數的情況下結束對象的生命週期,例如我重新使用它的內存來處理另一個對象,但這非常特殊。

+0

關於答案1,如果我寫'Picture a;圖片b = a'圖片B的複製生成器將被調用。那麼爲什麼'圖片temp = frame(my_pic)'不會調用它的複製構造函數?是不是'框架'生成一個'圖片',並用它來調用'圖片溫度的複製構造? – shengy 2011-12-31 16:43:35

+0

@shengy:'Picture temp = frame(my_pic);'指定複製初始化,它可以_may_調用複製構造函數,但臨時和複製構造函數可能會被忽略,在這裏就是這種情況。你問爲什麼複製構造函數在_after_這個陳述之後被調用,這是一個不同的問題。 – 2011-12-31 16:47:47

1

的拷貝構造函數,只要你認爲它可能或應該叫不一定叫:

以下情況可能會導致一個拷貝構造函數的調用:

  1. 當一個對象由返回值
  2. 當對象通過值傳遞(給函數)作爲參數
  3. 當對象被拋出
  4. 當對象被捕獲
  5. 當物體被放置在大括號內的初始化列表

這些情況統稱副本初始化並且等同於:T x = a;

然而, 不能保證一個拷貝構造將在這些情況下被調用, 因爲C++標準允許編譯器優化副本遠在某些情況下 ,一個例子是return value optimization (有時稱爲作爲RVO)。

From Wikipedia.

任何東西的析構函數的棧上,當它超出範圍時調用。

+0

但爲什麼當我使用語句'Picture temp = frame(my_pic)'時不調用Picture的副本構造函數? – shengy 2011-12-31 09:23:07

+0

[可能因爲這個,在我的答案中提到]。(http://en.wikipedia.org/wiki/Return_value_optimization) – 2011-12-31 09:24:04

+0

@shengy:但它被調用,請參閱您發佈的輸出的第2行。 – 2011-12-31 09:24:38

0

注意:在所有的答案中說複製構造函數將被調用它可能不會因爲編譯器做了一些優化。

1)爲什麼不在Picture temp = frame(my_pic)之後調用複製構造函數?

Picture temp = frame(my_pic);是return語句之前的最後一行,因此在程序被刪除之後發生的所有事情(被稱爲析構函數,堆棧和堆被清除)並結束。

2)爲什麼析構函數被調用?

析構函數(在這裏的每種情況下)都是因爲程序關閉而被調用的。注意:雖然這確實發生在程序結束時,但這並不意味着您不應該自行清理!

3)在Picture frame(const Picture& pic)中,如果函數被調用,是否會調用複製構造函數?

不可以。您沒有創建一個副本,您傳遞了一個引用,並創建一個新引用,編譯器將優化返回副本。

4)如果我改變Picture frame(const Picture& pic)Picture frame(Picture p),將拷貝構造函數時兩次調用該函數叫什麼名字?

不,它可能會在您輸入函數時調用,但編譯器會優化返回中的副本。

5)何時會調用複製構造函數?當這個類被函數按值返回時會發生嗎?當然後類通過值傳遞給一個函數?

拷貝構造函數會在兩種情況下被調用。

6)當將析構函數被調用?每次變量的生命週期結束?這是否意味着如果我通過值傳遞一個變量給一個函數,它的析構函數將在函數執行後被調用?

當對象被銷燬時將調用析構函數。這可能是因爲你銷燬了它或包含它的函數返回(結束),並且它的變量/對象從堆棧中刪除,或者在某些情況下(在程序結束時)從堆中移除。

+0

'圖片temp = frame(my_pic);'是_copy initialization_,它不一定會導致對複製構造函數的調用。在這種情況下,'Picture'的拷貝構造函數實際上只是在'Frame_Pic'的成員初始化列表中初始化時生成的:'[:]:p(pic){' – 2011-12-31 09:53:15

+0

@Charles Bailey Typo編輯答案,nice catch !謝謝。 – vdbuilder 2011-12-31 10:03:52

相關問題