2012-12-21 287 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