http://blog.sina.com.cn/s/blog_6ab0b9a801019b3h.html
上一篇文章中提到了一种使用副本构造函数初始化类对象的情况,这里也会陈述几种情况。
参考网站:http://www.91tech.net/Article/SoftTech/vctech/200410/118.html
网站上没有陈述上篇文章提到的调用副本构造函数初始类对象的情况外,不过讲解了其他的三种情况:
(1)明确表示由一个对象初始化另一个对象时;//其实这一点可用(2)来理解,本质上相同
(2)当对象作为函数实参传递给函数作形参时;
(3)当对象作为函数值进行返回时。
第一种比较好理解,这里不再陈述什么了;第二种情况正常理解即可,当对象作为参数传递给函数时,
要创建该对象的“临时副本”(Horton->P252),因此会调用副本构造函数,此时为了防止创建临时类对象
的副本,常向函数传递类对象的“引用”;第三种情况也是正常理解即可,当对象作为返回值时,系统会制
作返回值的“副本”,且系统总会自动制作函数返回值的副本(Horton->P249,P255)。
调用了副本构造函数初始化类对象,当然超出作用域后要进行“析构”。在第二种情况中,临时副本会
在该函数的调用结束时析构“临时副本”;第三种情况中当调用return (类对象)时调用副本构造函数创建
了副本,并作为返回值,当该返回值用完后即会调用“析构函数”进行释放内存。
(1)程序仿真验证
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class B
{
private:
int data;
public:
B(){cout<<"default constructor"<<endl;}
~B(){cout<<"destructed"<<endl;}
B(int i):data(i){cout<<"constructed by parameter "<<data<<endl;}
B(const B& copy){cout<<"copy constructor is called."<<endl;data = copy.data;}
B& operator=(const B& b){cout<<"the assignment operator overloading."<<endl;data = b.data;return *this;}
};
B Play(B b)
{
return b;
}
int main()
{
B b(500);
B temp;
temp = Play(b);
return 0;
}
仿真结果:
(2)做些修改探讨一些其他的问题
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class B
{
private:
int data;
public:
B(){cout<<"default constructor"<<endl;}
~B(){cout<<"destructed"<<endl;}
B(int i):data(i){cout<<"constructed by parameter "<<data<<endl;}
B(const B& copy){cout<<"copy constructor is called."<<endl;data = copy.data;}
B& operator=(const B& b){cout<<"the assignment operator overloading."<<endl;data = b.data;return *this;}
};
B Play(B b)
{
return b;
}
int main()
{
B b(500);
B temp = Play(b); //此时不会调用重载的赋值运算符
return 0;
}
程序的修改见红色部分,结合上篇文章的讲解,此处理论上会调用三次“副本构造函数”,第一次是Play()函数中创建对象b的副本,第二次是return语句创建副本,第三次就是上篇文章所讲解的声明类对象的同时初始化也会调用副本构造函数,但是实际上输出结果共调用了两次副本构造函数,结果如下:
第一次调用的副本构造函数是Play()函数中创建对象b的副本,当然第一次的析构也是析构的该副本。好像第二次和第三次的副本构造函数“合二为一”了,最后的两个析构函数当然是析构对象b和temp。
注意:若要按照前面调用三次副本构造函数的话则整段程序会调用4次析构函数。
经过该程序的分析若修改(2)中的main函数为:
int main()
{
B b(500);
B temp(Play(b));
return 0;
}
按照原来的分析也会是调用三次副本构造函数,不过经过上面的分析,猜测返回值的副本构造函数也和第三次的副本构造函数“合二为一”,也就是说与上面的仿真结果相同,经过验证确实相同,结果如下:
因此自己做了一个自己想当然认为的总结:当类对象的初始化确定采用副本构造函数进行初始化时,且该类对象的参数来自返回值为类对象的函数时,此时会将这两个副本构造函数“合二为一”。
(3)对程序又做了一些修改,看看结果如何
在(2)的基础上将main函数修改为
int main()
{
B temp = Play(5);//不会调用重载的赋值运算符
return 0;
}
结果如下:
Play()函数需要B类型的变量,在传递参数5时进行了隐式的类型转换,此时调用了B(int i)构造函数,由于向Play()函数传递的是整型参数5而不是类对象,故此处不再调用副本构造函数,即使进行的隐式的类型转换,也不会在类型转换的基础上再一次的进行调用副本构造函数。当遇到返回值为类对象的return语句时知道会调用副本构造函数,同时由于声明类对象的同时初始化也会调用副本构造函数,利用上面所讲的“合二为一”的理解方法,此处也是只会调用一次副本构造函数。当超出作用域时会调用析构函数:析构隐式类型转换所创建的类对象、析构创建的temp对象。
(4)再次修改(杂交以上所有的分析)
将(2)中的main函数修改为:
int main()
{
B temp;
temp = Play(5);
return 0;
}
输出结果为:
分析:
(a)B temp;->default constructor
(b)隐式的类型转换调用B(int i)->constructed by parameter 5,由于传递给函数Play()的参数不是 类对象,因此此处不再调用副本构造函数,该原因(3)中也讲了
(c)遇到了返回值为类对象的return语句,因此会创建类对象的副本,因此会调用副本构造函数从而输出->copy constructor is called
(d)执行完return语句后,隐式类型转换所构造的对象就超出了作用域,因此会调用析构函数->destructed
(e)由于要将return返回的类对象初始化temp,且temp时已经声明过的,故此处会调用重载的赋值运算符->the assignment operator overloading
(f)用过return返回的类对象通过重载的赋值运算符初始化完类对象temp后,返回的类对象失效,因此会调用析构函数进行销毁,->destructed
(g)temp对象超出作用域,进行析构->destructed