您的位置:首页 > 编程语言 > C语言/C++

Effective C++(十一):为需要动态分配内存的类声明一个复制构造函数和一个赋值操作符

2012-10-08 17:35 309 查看
首先看一个例子:
//一个很简单的string类
class string{
public:
string(const char* value);
~string;
...

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; }
请注意这里没有声明赋值操作符和复制构造函数,会造成一些不良后果。
如果这样定义两个对象:
string a("hello");
string b("world");
其结果会如下所示:
a:data->"hello\0"
b:data->"world\0"
如果进行如下复制操作
b = a;
因为没有自定义的operator=调用,C++会生成并调用一个缺省的operator=操作符。这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作。对指针来说是逐位拷贝。
在这种情况下会至少存在两个问题:
1.b层指向的内存永远不会被删除,因为永远丢失。这是产生内存泄漏的典型例子。
2.现在的a和b指向同一个字符串,那么只要有其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。
string a("hello");   //定义并构造a{string b("world");  //开一个新的内存空间//定义并构造b...b = a;				//执行operator=,//丢失b的内存}string c = a;           //c.data的值不能确定//a.data已被删除
例子中最后一个语句调用了拷贝构造函数,因为它也没有在类中定义,C++以与处理赋值操作符一样的方式生成一个复制构造函数并执行相同的动作:对对象里的指针进行逐位拷贝。这会导致同样的问题,但不用担心内存泄漏,因为被初始化的对象还不能指向任何的内存。比如上面代码中的情形,当c.data用a.data的值来初始化时没有内存泄漏,因为c.data没指向任何地方。不过,假如c被a初始化后,c.data和a.data指向同一个地方,那这个地方会被删除两次:一次在c被摧毁时,另一次在a被摧毁时。复制构造函数的情况和赋值操作符还有点不同。在传值调用的时候,它会产生问题。void donothing(string localstring) {}string s = "the truth is out there";donothing(s);一切好象都很正常。但因为被传递的localstring是一个值,它必须从s通过(缺省)复制构造函数进行初始化。于是localstring拥有了一个s内的指针的拷贝。当donothing结束运行时,localstring离开了其生存空间,调用析构函数。其结果也将是:s包含一个指向localstring早已删除的内存的指针。顺便指出,用delete去删除一个已经被删除的指针,其结果是不可预测的。所以即使s永远也没被使用,当它离开其生存空间时也会带来问题。解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的复制构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。对于有些类,当实现复制构造函数和赋值操作符非常麻烦的时候,特别是可以确信程序中不会做拷贝和赋值操作的时候,去实现它们就会相对来说有点得不偿失。前面提到的那个遗漏了复制构造函数和赋值操作符的例子固然是一个糟糕的设计,那当现实中去实现它们又不切实际的情况下,该怎么办呢?很简单,照本条款的建议去做:可以只声明这些函数(声明为private成员)而不去定义(实现)它们。这就防止了会有人去调用它们,也防止了编译器去生成它们。

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐