构造函数,析构函数和赋值操作符
几乎所有的对象都需要构造函数、拷贝构造函数、析构函数和赋值构造函数,但问题是何时需要自己定义这些函数,何时调用默认的这些函数?这也是所谓的“浅拷贝”和“深拷贝”的问题。
当类的数据成员有指针,需要动态的为指针申请内存时,这时就需要自己定义拷贝构造函数和赋值构造函数,这就是所谓的“深拷贝”。
当类的数据成员没有指针,不需要动态的为指针申请内存时,不需要定义自己版本的拷贝和赋值构造函数,直接调用默认的即可,这就是所谓的“浅拷贝”。
为何这种情况需要定义自己的拷贝构造函数和赋值构造函数?看下面的例子:
#include <iostream>
using namespace std;
// 一个很简单的TString类
class TString {
public:
TString(const char *value);
~TString();
void Print();
private:
char *data;
};
TString::TString(const char *value)
{
if (value) {
data = new char[strlen(value) + 1];
strcpy(data, value);
}
else {
data = new char[1];
*data = '\0';
}
cout << "Call Constructor !\n";
}
inline TString::~TString() { delete [] data; cout << "Call Destructor !\n";}
void TString::Print()
{
cout << "The content is :" << data << endl;
}
void donothing(const TString &localstring) { cout << "Call donothing function!\n";}
int main()
{
TString a("hello");
TString b("world");
//b = a;
b.Print();
a.Print();
}
输出为:
Call Constructor !
Call Constructor !
The content is :world
The content is :hello
Call Destructor !
Call Destructor !
但是如果定义:b = a; 就会提示错误,为何呢?类本身不是提供默认的赋值构造函数吗,为何还会出错?
如果b = a;而没有定义自己的赋值构造函数,TString b("world");为b对象申请的内存段将丢失,也即没有任何指针指向这个内存段,这就是所谓的内存泄露。而b = a;会通过默认的赋值构造函数直接把指针a所指向的地址赋值给b,那么b和a指向同一段地址,当a和b离开生存空间时会调用类的析构函数来删除申请的地址,比如当b删除了b.data所指向的地址后,a对象也会调用析构函数删除a.data指向的地址,但此地址已经被b对象删除,用delete删除一个已经被删除的指针,其结果是不可预料的。
这就是为什么当类的数据成员是指针时,需要字定义拷贝构造函数和赋值构造函数。
又如:
TString s = "the truth is out there";
donothing(s);
如果没有定义自己的拷贝构造函数,会出现同样的问题,说明如下:
一切好象都很正常。但因为被传递的localstring是一个值,它必须从s通过(缺省)拷贝构造函数进行初始化。于是localstring拥有了一个s内的指针的拷贝。当donothing结束运行时,localstring离开了其生存空间,调用析构函数。其结果也将是:s包含一个指向localstring早已删除的内存的指针。
解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你
可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 m29)去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。
在看下面的例子:
#include <iostream>
using namespace std;
class CSomething
{
public:
int a;
int b;
public:
CSomething(int a, int b)
{
cout << "Call CSomething Constructor !" << endl;
this->a = a; this->b = b;
}
~CSomething()
{
cout << "Call the CSomething Destructor!" << endl;
}
};
class CA
{
private:
CSomething* sth; // 以指针形式存在的成员变量
public:
CA(CSomething* sth)
{
cout << "Call CA Constructor!" << endl;
this->sth = new CSomething(sth->a, sth->b);
}
~CA()
{
cout << "In the destructor of class CA..." << endl;
if (NULL != sth) delete sth;
}
void Show(){cout << "(" << sth->a << ", " << sth->b << ")" << endl;}
void setValue(int a, int b){sth->a = a; sth->b = b;}
void getSthAddress()
{
cout << sth << endl;
}
};
int main(void)
{
CSomething sth(1, 2);
CA ca(&sth);
ca.Show();
CA cb(ca); // 调用缺省的隐式拷贝构造函数
cb.Show();
cb.setValue(2, 3);
ca.Show();
cb.Show();
ca.getSthAddress();
cb.getSthAddress();
return 0;
}
没有显示定义拷贝构造函数,其结果输出如下:
Call CSomething Constructor !
Call CA Constructor!
Call CSomething Constructor !
(1, 2)
(1, 2)
(2, 3)
(2, 3)
00396560
00396560
In the destructor of class CA...
Call the CSomething Destructor!
In the destructor of class CA...
Call the CSomething Destructor!
由输出结果可以看出:ca和cb的sth是同一个。
运行时会出错,其出错原因时程序结束时调用析构函数delete sth,由于ca.sth地址:00396560 和cb.sth地址:00396560相同,当ca.sth删除掉后,cb.sth再删除时就会出现问题。
下面是加上CA的拷贝构造函数:
CA( const CA & ob)
{
sth = new CSomething((ob.sth)->a,(ob.sth)->b);
cout << "Call the Copy Constructor!" << endl;
}
其输出为:
Call CSomething Constructor !
Call CA Constructor!
Call CSomething Constructor !
(1, 2)
Call CSomething Constructor !
Call the Copy Constructor!
(1, 2)
(1, 2)
(2, 3)
00396560
00396610
In the destructor of class CA...
Call the CSomething Destructor!
In the destructor of class CA...
Call the CSomething Destructor!
Call the CSomething Destructor!
由输出结果:
修改值后的ca.sth->a ca.sht->b
(1, 2)
修改值后的cb.sth->a cb.sht->b
(2, 3)
ca.sth:00396560
cb.sth00396610
可以看出达到了”深拷贝“的效果。