我们知道C++ 编写程序的一个特点就是需要程序员管理内存,也就是需要的时候new一个对象,在合适的时候释放申请的内存。如果没有释放掉申请的内存会造成内存泄漏,多次释放同一块内存也会造成程序异常。所以对于指针的操作需要特别小心。
C++编写类时,如果类中包含指针成员,就需要特别小心拷贝构造函数的编写,因为很容易造成内存泄漏。例如以下情况:
- 浅拷贝造成内存泄漏的例子
#include <iostream>
using namespace std;
class HasPtrMem
{
public:
HasPtrMem() :d(new int(0)) {
}
//不定义拷贝构造函数时,编译器生产默认拷贝构造函数
//HasPtrMem(HasPtrMem &rhs) :d(new int(*rhs.d)) {}
~HasPtrMem() {
delete d; }
int *d; //指针成员
};
void main(int argc, char*argv[])
{
HasPtrMem a;
HasPtrMem b(a); //拷贝构造
cout << *a.d << std::endl;
cout << *b.d << std::endl;
}
首先定义一个包含指针成员的HasPtrMem类。在构造函数中申请内存,在析构函数中释放内存。
然后在main函数中申明一个HasPtrMem变量a, 并用a初始化变量b,
类没有定义拷贝构造函数时,编译器会隐式地(隐式表示如果不被使用则不生成)生成拷贝构造函数,在拷贝构造函数中对成员变量执行类似于memcpy的按位复制。执行默认拷贝函数后,指针成员变量会指向同一块堆内存。
在本例中的两个HasPtrMem对象a,b中的指针都指向了同一块推内存,在析构函数中两次释放内存时就会造成异常。
本例中两个对象的拷贝方式也就是浅拷贝,如果改成深拷贝就不会出现异常了。
- 深拷贝例子
#include <iostream>
using namespace std;
class HasPtrMem
{
public:
HasPtrMem() :d(new int(0)) {
}
HasPtrMem(HasPtrMem &rhs) :d(new int(*rhs.d)) {
}// 复制构造函数;深拷贝
~HasPtrMem() {
delete d; }
int *d;
};
void main(int argc, char*argv[])
{
HasPtrMem a;
HasPtrMem b(a);
cout << *a.d << std::endl;
cout << *b.d << std::endl;
}
浅拷贝 && 深拷贝
- 浅拷贝(shallow copy)
增加一个指针,指向一个已经纯在的内存。实现了逻辑上的拷贝。
- 深拷贝(deep copy)
新申请一块内存,并把数据拷贝到新的内存。实现逻辑和物理上的拷贝。
浅拷贝造成的内存泄漏问题,解决办法有
- 可以修改拷贝构造函数和赋值操作符,实现深拷贝。
- 使用智能指针share_ptr,通过引用计数解决重复释放内存问题。
拷贝影响性能
除了复制对象时调用拷贝构造函数外,还有以下情况
1) 函数参数为对象,实参传递给形参的实际上是拷贝对象。
2) 函数的返回值为对象,实际是函数内对象的一个拷贝,用于返回函数调用处。
例如以下情况
#include <iostream>
using namespace std;
class HasPtrMem
{
public:
HasPtrMem() :d(new int(0)) {
std::cout << "construct: " << ++n_constructor << std::endl; }
HasPtrMem(HasPtrMem &rhs) :d(new int(*rhs.d)) // 拷贝构造函数,深拷贝
{
std::cout << "copy construct: " << ++n_copyConstructor << std::endl;
}
~HasPtrMem() {
delete d;
std::cout << "destruct: " << ++n_destructor << std::endl;
}
int *d;
static int n_constructor;
static