3.3 右值引用:移动语义和完美转发

类别:类作者

3.3.1 指针成员与拷贝构造

对C++程序员来说,编写C++程序有一条必须注意的规则,就是在类中包含了一个指针成员的话,那么就要特别小心拷贝构造函数的编写,因为一不小心,就会出现内存泄露。我们来看看代码清单3-16中的例子。

代码清单3-16


include <iostream>

using namespace std;

class HasPtrMem{

public:

HasPtrMem():d(new int(0)){}

HasPtrMem(const HasPtrMem&h):

d(new int(h.d)){}//拷贝构造函数,从堆中分配内存,并用h.d初始化

~HasPtrMem(){delete d;}

int*d;

};

int main(){

HasPtrMem a;

HasPtrMem b(a);

cout<<*a.d<<endl;//0

cout<<*b.d<<endl;//0

}//正常析构

//编译选项:g++3-3-1.cpp


在代码清单3-16中,我们定义了一个HasPtrMem的类。这个类包含一个指针成员,该成员在构造时接受一个new操作分配堆内存返回的指针,而在析构的时候则会被delete操作用于释放之前分配的堆内存。在main函数中,我们声明了HasPtrMem类型的变量a,又使用a初始化了变量b。按照C++的语法,这会调用HasPtrMem的拷贝构造函数。这里的拷贝构造函数由编译器隐式生成,其作用是执行类似于memcpy的按位拷贝。这样的构造方式有一个问题,就是a.d和b.d都指向了同一块堆内存。因此在main作用域结束的时候,a和b的析构函数纷纷被调用,当其中之一完成析构之后(比如b),那么a.d就成了一个“悬挂指针”(dangling pointer),因为其不再指向有效的内存了。那么在该悬挂指针上释放内存就会造成严重的错误。

这个问题在C++编程中非常经典。这样的拷贝构造方式,在C++中也常被称为“浅拷贝”(shollow copy)。而在未声明构造函数的情况下,C++也会为类生成一个浅拷贝的构造函数。通常最佳的解决方案是用户自定义拷贝构造函数来实现“深拷贝”(deep copy),我们来看看代码清单3-17中的修正方法。

代码清单3-17


include <iostream>

using namespace std;

class HasPtrMem{

public:

HasPtrMem():d(new int(0)){}

HasPtrMem(HasPtrMem&h):

d(new int(h.d)){}//拷贝构造函数,从堆中分配内存,并用h.d初始化

~HasPtrMem(){delete d;}

int*d;

};

int main(){

HasPtrMem a;

HasPtrMem b(a);

cout<<*a.d<<endl;//0

cout<<*b.d<<endl;//0

}//正常析构

//编译选项:g++3-3-2.cpp


在代码清单3-17中,我们为HasPtrMem添加了一个拷贝构造函数。拷贝构造函数从堆中分配新内存,将该分配来的内存的指针交还给d,又使用(h.d)对d进行了初始化。通过这样的方法,就避免了悬挂指针的困扰。