2012-12-21 210 views
0

用下面的C++程序:奇怪的拷貝構造函數

#include <memory> 
#include <iostream> 

using namespace std; 

struct my_class{ 
    int value; 

    my_class(int id): value(id){ 
     cout<<"constructing "<<id<<endl; 
     cout<<"address is "<<static_cast<const void *>(this)<<endl; 
    } 

    my_class(const my_class & a){ 
     cout<<"construct copying "<<a.value<<endl; 
     cout<<static_cast<const void *>(this)<<"<-"<<static_cast<const void *>(&a)<<endl; 
    } 

    my_class operator=(const my_class & a){ 
     cout<<"assignment copying "<<a.value<<endl; 
     this->value = a.value; 
     cout<<static_cast<const void *>(this)<<"<-"<<static_cast<const void *>(&a)<<endl; 
     return *this; 
    } 

    ~my_class(){ 
     cout<<"deleting "<<this->value<<endl; 
     cout<<"address is "<<static_cast<const void *>(this)<<endl; 
    } 
}; 

my_class f(){ 
    cout<<"==in f=="<<endl; 
    my_class temp(2); 
    cout<<"==out f=="<<endl; 
    return temp; 
} 

int main(){ 
    cout<<"==in main=="<<endl; 
    my_class a(1); 

    a = f(); 

    a.value++; 
    cout<<"==out main=="<<endl; 
    return 0; 
} 

我得到了以下結果:

==== 

==in main== 

constructing 1 

address is 0x28ff04 

==in f== 

constructing 2 

address is 0x28ff0c 

==out f== 

assignment copying 2 

0x28ff04<-0x28ff0c 

construct copying 2 

0x28ff08<-0x28ff04 

deleting 2686868 

address is 0x28ff08 

deleting 2 

address is 0x28ff0c 

==out main== 

deleting 3 

address is 0x28ff04 


=== 

誰能向我解釋與地址「0x28ff08」的對象會發生什麼和從地址「0x28ff04」的對象構造相關副本?我真的不明白爲什麼複製構造函數在這裏被調用。


我不知道如果我得到這個正確的,因此我想進一步詳細解釋它。任何人都會發現我的錯誤,請指出。

首先,圖像示出執行流的詳細信息: the execution flow

(1)。創建一個值爲1的對象a;

(2)。呼叫功能f()。創建一個對象temp,編譯器發現該對象將被返回,所以它直接在調用者的堆棧中創建;

(3)。通過調用aoperator=()將返回的對象f()(即,temp)分配給對象a;

(4)。對象a作爲參數(rvalue)使用相同的變量名稱a傳入operator=()

(5)。方法operator=()被調用main::a(左值,與濫用符號),因此this在函數指向main::a,[!!這是困惑我的部分];

(6)。 operator=()main::a的值改變爲a的值(即從1到2);

(7)。編譯器發現返回類型不是引用,並且main()中已存在*this,因此它必須通過調用複製構造函數複製*this。但是,複製構造函數不會初始化該對象,因此會創建一個未初始化的對象。

(8)。 [!!對此部分不太確定]左值和生成的對象是同一個對象,因此沒有對象因優化而返回。

(9)。根據@Mike Seymour的說法,被複制的對象被銷燬,因爲編譯器不能忽略它,因爲構造器和析構器都會做某些事情(例如輸出值和地址)。

(10)。當退出operator=()時,對象a被銷燬。

(11)。當退出main()時,對象main::a最終被銷燬。

上面解釋了輸出,但是,我目前的理解可能不正確。如果我錯了,請幫助我理解這一點。非常感謝。

+1

您的輸出與代碼中的日誌記錄不匹配。 – Useless

+1

此代碼具有未定義的行爲,因爲當通過複製構造函數創建對象時,您正在使用'value'而不初始化它。 –

+0

複製構造函數創建的對象從不使用。正如你所看到的,只要程序離開復制分配,對象就會被銷燬。順便說一句,我沒有提供默認構造函數,爲什麼可以在不初始化的情況下創建對象。 – RainSia

回答

2

的拷貝構造函數被調用,因爲你的賦值運算符返回*this副本。正如其他人所指出的,賦值運算符應該返回一個引用而不是副本;既避免不必要的複製,又允許操作員鏈接。

也許你想問的問題是,爲什麼從賦值操作符返回值涉及一個副本,而從f()返回不是?

f()正在返回本地對象的副本,從該函數返回後不需要保留該副本。這允許編譯器執行返回值優化,其中要返回的變量存儲在調用方可訪問的某個位置,並且成爲返回值而不復制或移動它。

operator=()正在返回一個持久對象的副本。由於原始對象仍然存在,並且必須與返回值分開,所以在這裏需要一個副本。

或者你想問一下,爲什麼編譯器不消除複製,因爲複製的對象從來沒有使用過?那是因爲你已經給了構造函數和析構函數的副作用,並且不允許編譯器消除這些副作用。

+0

是的。這正是我想弄明白的。現在,我想我理解這個程序的行爲。謝謝。 – RainSia

4

這是因爲您的賦值操作符返回對象的副本。在這裏:

my_class operator=(const my_class & a) 

使用的參考,而不是:

my_class& operator=(const my_class & a) 
+1

是的,我知道在真正的項目中我應該使用my_class&。但我只是對這個程序的行爲感到好奇。由於我沒有提供默認構造函數,爲什麼可以創建具有未初始化值的對象?我無法想象程序運行時發生了什麼。 – RainSia

+0

您確實提供了拷貝構造函數:'my_class(const my_class&a)',但它(您的代碼)不會將值複製到新構建的對象中。 –

+0

我明白了你的觀點。但是你認爲這個對拷貝構造函數的調用是完全沒有必要的嗎?由默認構造函數創建的對象在創建後立即銷燬。我不知道如何表達這一點,但我的觀點是,由於創建的對象就像是一個臨時對象,爲什麼編譯器沒有爲了優化目的而消除它。 – RainSia