析构遇上拷贝—“被两次调用的析构”
类的构造函数(包括初始化列表)的使用稀松平常
但当析构函数遇上拷贝初始化,就有可能出现一些有意思的事情
涉及:赋值符重载函数参数应当是引用类型,否则可能导致意外的构造
到上面第三句已经是这篇文章的全部内容了,若需要实例说明,如下:
意外的析构(析构函数调用次数多于生命的对象数)
且看代码:
//我们假设有一个类:
class catcher{
public:
catcher(const string& s):
name(s)//初始化列表
{
cout<<"catcher()"<<endl;
}
catcher(const catcher& z)//拷贝
{
cout << "catcher(const catcher&)" << endl;
name = z.name;
}
/*赋值运算符重载---关键所在*/
catcher& operator=(const catcher k){
cout << "catcher& operator=(const catcher k)" << endl;
name = k.name;
return *this;
}
~catcher() { //析构
cout << "~catcher()" << name << endl;
}
void chName(const string& s) {name = s;}
private:
string name="init value";
};
为了显示各个成员函数的调用情况,我们在进入每个函数后都输出函数原型到控制台
详细的我们在这个类使用后,再细说
//使用用这个类
int main()
{
//初始化两个对象Markson和Hancici
catcher Markson("Markson");
catcher Hancici("Hancici");
Hancici = Markson;//使用重载的运算符将Markson的name拷贝给Hancici
return 0;
}
输出结果:
catcher()
catcher()
catcher(const catcher&)
catcher& operator=(const catcher k)
~catcher()Markson
~catcher()Markson
~catcher()Markson
从输出文本的首尾可以看到,只调用了两次构造函数,但程序结束时却调用了3次析构函数。两个对象,三次析构,明显是不合理的。推测,存在除markson和Hancici之外的第三个被构造的对象。
观察输出结果中间部分发现,本来main()只调用了赋值运算符的重载函数,但结果却多了一次对拷贝拷贝函数的调用,这儿也许是问题所在。
再看赋值符重载函数的参数——非引用类型。这意味着在传参时会对参数对象做一次拷贝,此时参数对象是一个catcher对象,自然地会调用拷贝构造函数
catcher(const catcher&)构造一个新的对象,也即是原型catcher& operator=(const catcher k)
中的参数k。所以z就是那个“除Markson和Hancici之外第三个被构造的对象”
我们把赋值符重载函数作如下修改:
catcher& operator=(const catcher k) ----> catcher& operator=(const catcher& k)
//记得同时修改输出语句中的函数原型文本
再编译运行得到如下输出:
catcher()
catcher()
catcher& operator=(const catcher& k)
~catcher()Markson
~catcher()Markson
“意外的析构”消失了,ok。
赋值符重载函数参数应当是引用类型,否则可能导致意外的构造
涉及:函数的引用类型和非引用类型参数