使用C++11解决内存泄露的问题(shared_ptr共享智能指针)

    C#和Java中有自动垃圾回收机制,.NET运行时和Java虚拟机可以管理分配的堆内存,在对象失去引用时自动回收,因此,在C#和Java中,内存管理不是大问题。但是C++语言没有垃圾回收机制,必须自己去释放分配的堆内存,否则就会内存泄露。相信大部分C++开发人员都遇到过内存泄露的问题,而查找内存泄露的问题往往要花大量的精力。解决这个问题最有效的办法是使用智能指针(SmartPointer)。使用智能指针就不会担心内存泄露的问题了,因为智能指针可以自动删除分配的内存。智能指针和普通指针的用法类似,只是不需要手动释放内存,而是通过智能指针自己管理内存的释放,这样就不用担心忘记释放内存从而导致内存泄露了。
    智能指针是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保在离开指针所在作用域时,自动正确地销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数。每使用它一次,内部的引用计数加1,每析构一次,内部引用计数减1,减为0时,删除所指向的堆内存。
    C++11提供了3种智能指针:std::shared_ptr、std::unique_ptr和std::weak_ptr,使用时需要引用头文件,本章将分别介绍这3种智能指针。

shared_ptr共享的智能指针

std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

shared_ptr基本用法

1.初始化
可以通过构造函数、std::make_shared<T>辅助函数和reset方法来初始化shared_ptr,代码如下:

std::shared_ptr<int> p(new int(1));
std::shared_ptr<int> p2 = p;
std::shared_ptr<int> ptr;
ptr.reset(new int(1));
if(ptr){
	cout<<"ptr is not null"<<endl;
}

我们应该优先使用make_shared来构造智能指针,因为它更加高效。
不能将一个原始指针直接赋值给一个智能指针,例如,下面这种方法是错误的:

std::shared_ptr<int> p = new int(1); //编译报错,不允许直接赋值

可以看到智能指针的用法和普通指针的用法类似,只不过不需要自己管理分配的内存。shared_ptr不能通过直接将原始指针赋值来初始化,需要通过构造函数和辅助方法来初始化。对于一个未初始化的智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。另外,智能指针可以通过重载的bool类型操作符来判断智能指针中是否为空(未初始化)。

2.获取原始指针
当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下:

std::shared_ptr<int> ptr(new int(1));
int* p = ptr.get();

3.指定删除器
智能指针初始化可以指定删除器,代码如下:

void DeleteIntPtr(int* p)
{
	delete p;
}
std::shared_ptr<int> p(new int,DeleteIntPtr);

当p的引用计数为0时,自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达式,因此,上面的写法还可以改为:

std::shared_ptr<int> p(new int,[](int* p){delete p;});

当我们用shared_ptr管理动态数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象,代码如下:

std::shared_ptr<int> p(new int[10],[](int* p){delete[] p;}); //指定delete[]

也可以将std::default_delete作为删除器。default_delete的内部是通过调用delete来实现功能的,代码如下:

std::shared_ptr<int> p(new int[10],std::default_delete <int[]>);

另外,还可以通过封装一个make_shared_array方法来让shared_ptr支持数组,代码如下:

template<typename>
shared_ptr<T> make_shared_array(size_t size)
{
	return shared_ptr<T>(new T[size],default_delete<T[]>());
}

测试代码如下:

std::shared_ptr<int> p = make_shared_array<int>(10);
std::shared_ptr<char> p = make_shared_array<char>(10);

使用shared_ptr需要注意的问题

智能指针虽然能自动管理堆内存,但它还是有不少陷阱,在使用时要注意。

1)不要用一个原始指针初始化多个shared_ptr,例如下面这些是错误的:

int* ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); //logic error

2)不要在函数实参中创建shared_ptr。对于下面的用写法:

function(shared_ptr<int>(new int),g());//有缺陷

因为C++的函数参数的计算顺序在不同的编译器不同的调用约定下可能是不一样的,一般是从右到左,但也有可能是从左到右,所以,可能的过程是先newint,然后调g(),如果恰好g()发生异常,而shared_ptr还没有创建,则int内存泄露了,正确的写法应该是先创建智能指针,代码如下:

shared_ptr<int> p(new int());
f(p,g());

3)通过shared_from_this()返回this指针。不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构,看下面的例子。

struct A
{
	shared_ptr<A> GetSelf()
	{
		return shared_ptr<A>(this); //don't do this!
	}
};

int main()
{
	shared_ptr<A> sp1(new A);
	shared_ptr<A> sp2 = sp1->GetSelf();
	return 0;
}

在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1和sp2,而它们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。
正确返回this的shared_ptr的做法是:让目标类通过派生std::enable_shared_from_this<T>类,然后使用基类的成员函数shared_from_this来返回this的shared_ptr,看下面的示例。

class A:public std::enable_shared_from_this<A>
{
	std::shared_ptr<A> GetSelf()
	{
		return shared_from_this();
	}
};
std::shared_ptr<A> spy(newA);
std::shared_ptr<A> p = spy->GetSelf(); //OK

至于用shared_from_this()的原因,将在<<unique_ptr独占智能指针>>一文给出解释。

4)要避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄露。下例是一个典型的循环引用的场景

struct A;
struct B;
struct A{
	std::shared_ptr<B> bptr;
	~A(){
	cout<<"A is deleted!"<<endl;
	}
};

struct B{
	std::shared_ptr<A> aptr;
	~B(){
	cout<<"B is deleted!"<<endl;
	}
};

void TestPtr()
{
	{
		std::shared_ptr<A> ap(new A);
		std::shared_ptr<B> bp(new B);
		ap->bptr = bp;
		ap->aptr = ap;
	}
	//Objects should be destroyed.
}

测试结果是两个指针A和B都不会被删除,存在内存泄露。循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不会减为0,导致两个指针都不会被析构,产生了内存泄露。解决办法是把A和B任何一个成员变量改为weak_ptr,具体方法将在<<weak_ptr弱引用智能指针>>中介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值