C++编译器会为每个类自动生成一个默认的构造函数、析构函数、赋值函数、拷贝构造函数,这当然是在你没有为你的类声明这些函数的时候。这些默认的功能函数在为你提供方便的时候,也会给你带来麻烦。
例如:
class string {
public:
string(const char *value);
~string();
... // 没有拷贝构造函数和operator=
private:
char *data;
};
string::string(const char *value)
{
if (value) {
data = new char[strlen(value) + 1];
strcpy(data, value);
}
else {
data = new char[1];
*data = '\0';
}
}
inline string::~string() { delete [] data; } //注意:new 和delete 要采用相同的形式。
如果有string的两个对象,
string a("hello");
string b("world");
当b=a时,因为你自己没为类定义那些函数,所以C++编译器会提供默认的赋值函数,这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data) 来说就是逐位拷贝。这种情况下至少有两个问题。
第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。
第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存,重复析构的问题。
下面的语句:
string a("hello"); // 定义并构造 a
{ // 开一个新的生存空间
string b("world"); // 定义并构造 b
...
b = a; // 执行 operator=, 调用默认赋值函数
// 丢失b的内存,造成内存泄露。
} // 离开生存空间, 调用
// b的析构函数
string c = a; // c.data 的值不能确定! 调用默认的拷贝构造函数
// 但是a.data 已被删除,无法进行拷贝构造。
当这类对象进行函数参数按值传递时,形参会按照缺省的拷贝构造函数进行初始化,形参拥有一个指向该对象指针的一个拷贝,当函数结束时,形参会调用析构函数,该对象的指针也被销毁。
class stack
{
public:
stack(const char *value);
~stack();
char * data;
};
stack::stack(const char *value)
{
if(value)
{
data= new char[strlen(value)+1];
strcpy(data,value);
}
else
{
data= new char[1];
*data = '\0';
}
}
inline stack::~stack()
{
delete []data;
}
void dosth(stack pstk)
{
cout<<pstk.data<<endl;
}
int main()
{
stack str("iamxczhang");
dosth(str);
}
在析构函数出设立断点,你会看到析构函数执行两次!
牢记:只要类里有指针变量就得自己写拷贝构造函数和赋值函数,但是你确定用不着这些函数时,可以把这些函数做private声明而不去实现它,这就防止了会有人去调用它们,也防止了编译器去生成它们。