智能指针

由于C++不支持垃圾自动回收机制,程序员必须手动释放动态申请的空间,否则会发生内存泄漏,这无疑对编程提出了更高的要求。为了解决令人头疼的内存泄漏问题,STL引入了智能指针。

智能指针实际上是一个类模板,对普通指针进行了一层封装,模板参数是指针指向的类型,通过重载->和*两个操作符使智能指针的用法与普通指针相同。通过析构函数释放指针指向的空间,使得内存管理完全由智能指针自动完成,无需手动释放。

由于C++11抛弃了传统的智能指针auto_ptr,因此在新代码中最好不要使用auto_ptr。C++11常用的三个智能指针包括:unique_ptr独享指针shared_ptr共享指针weak_ptr若指针

独享指针unique_ptr唯一拥有所指向对象的所有权,不支持拷贝和赋值操作,因此不能用unique_ptr对另一个智能指针初始化或赋值,而只能通过move函数将其所有权转移给其他智能指针,确保不和其他智能指针指向同一个对象。

unique<int> p1(new int(1));
unique<int> p2=p1;  //错误,不能进行赋值操作
unique<int> p2=std::move(p1);  //正确,可以通过move函数转移所有权

共享指针shared_ptr是最常见的智能指针,多个shared_ptr共享所指向对象的所有权,通过引用计数管理指向同一对象的智能指针个数,每增加一个智能指针指向对象时,引用计数加1,当指向同一对象的所有智能指针的生命周期都结束时,引用计数为0,此时释放对象的内存空间。

弱指针weak_ptr配合shared_ptr一起使用,weak_ptr可以与shared_ptr指向同一个对象,但是不改变引用计数的值。

shared_ptr<int> p1(new int(1));  //引用计数为1
shared_ptr<int> p2=p1;  //引用计数为2
weak_ptr<int> p3=p2;  //引用计数仍然为2

根据三种智能指针的性质不难看出,当对象无须共享所有权时,应该使用unique_ptr独享指针;当对象需要共享所有权时,应该使用shared_ptr指针;当需要与shared_ptr共享对象所有权而又不想改变引用计数时,应该使用weak_ptr弱指针。

案例分析

两个shared_ptr指针所指向对象的数据成员中,如果含有指向对方对象的shared_ptr指针则会产生环状引用。环状引用导致释放资源时发生死锁,引用计数不会降为0,造成对象空间无法释放。环状引用的定义比较晦涩,下面通过一个例子来解释:

class A
{
public:
	shared_ptr<B> bptr;
	~A()
	{
		cout<<"~A()"<<endl;
	}
};

class B
{
public:
	shared_ptr<A> aptr;
	~B()
	{
		cout<<"~B()"<<endl;
	}
};

int main()
{
	shared_ptr<A> a(new A());
	shared_ptr<B> b(new B());

	a->bptr=b;
	b->aptr=a;

	return 0;
}

程序中通过new创建了一个类A的对象和一个类B的对象并通过shared_ptr指向它们,之后通过指向另一个对象的智能指针对自身的数据成员ptr进行赋值,因此两个对象的引用计数都为2。

当程序退出时,main函数中创建的智能指针由于生命周期结束,其所指向对象的引用计数减1,但是由于环状引用,对象内部的智能指针的生命周期都不会结束,两个对象的引用计数始终为1。

具体来讲,就是对象A中的智能指针bptr只有在对象A析构之后才会结束其生命周期,从而将对象B的引用计数降为0;而对象A并不会被销毁,因为对象A销毁的条件是指向A的智能指针的引用计数降为0,而对象B中指向A的智能指针只有在B对象析构后才会结束其生命周期,从而将对象A的引用计数降为0。

因此上述逻辑就成了一个死结,就好像两个人打架都揪着对方的头发,并且叫嚣着只要对方松手自己就松手,但是两个人谁也不愿意首先让步,结果这两个人始终保持着同样的姿势,除非某一方实在坚持不住放弃了。

程序中不存在某个对象坚持不住首先释放的情况,因此必须处理环状引用问题。解决环状引用问题的钥匙就是weak_ptr,因为weak_ptr不会增加对象的引用计数。

class A
{
public:
	weak_ptr<B> bptr;
	~A()
	{
		cout<<"~A()"<<endl;
	}
};

class B
{
public:
	weak_ptr<A> aptr;
	~B()
	{
		cout<<"~B()"<<endl;
	}
};

int main()
{
	shared_ptr<A> a(new A());
	shared_ptr<B> b(new B());

	a->bptr=b;
	b->aptr=a;

	return 0;
}

将类中的智能指针类型由shared_ptr改为weak_ptr,在main函数中初始化两个智能指针时会将对象的引用计数加1,但是对类中的数据成员进行赋值时不会增加对象的引用计数。程序退出时,main函数中创建的两个智能指针生命周期结束,对象的引用计数由1减为0,对象空间释放。

unique_ptr和auto_ptr

首先,auto_ptr存在潜在的安全问题。auto_ptr允许赋值操作,只是赋值操作的含义是将指针指向对象的所有权转移给另一个auto_ptr指针,原指针在失去对象的所有权后成为空指针,如果后续程序错误的使用了这个空指针可能会发生潜在的问题,而unique_ptr从根本上禁止了赋值操作。

既然unique_ptr不支持赋值操作,那么如果函数的返回值是unique_ptr怎么办呢?是否能将函数返回值声明成unique_ptr呢?

实际上unique_ptr在赋值问题上做了折中:如果赋值给一个临时变量,则允许进行赋值操作。因为临时变量在赋值操作后会立即销毁,不会被使用,也就不会产生安全问题。

unique_ptr<int> p1(new int(1));
unique_ptr<int> p2=p1;  //不允许
unique_ptr<int> p3=unique_ptr<int> (new int(1));  //允许

其次auto_ptr不能作为容器的元素。由于容器中的对象需要支持拷贝构造函数,拷贝构造函数的参数为const类型,值不能改变,而auto_ptr在赋值时肯定会修改参数值,因为auto_ptr需要将参数中的指针置空,避免两个auto_ptr指向同一个对象,而unique_ptr解决了这个问题。

vector<auto_ptr<int>> vs;  //不允许
vector<unique_ptr<int>> vs;  //允许

最后auto_ptr不适用于动态数组。由于动态数组使用delete[ ]释放数组中所有元素的空间,而auto_ptr在释放对象空间时默认使用delete操作符,只会释放动态数组首元素的空间,造成内存泄漏,而unique_ptr则会正确使用delete[ ]释放整个动态数组的空间。

auto_ptr<int> p1(new int[10]);  //不允许
unique_ptr<int> p2(new int[10]); //允许
总结:
1、在可能出现环状引用的地方使用weak_ptr弱指针代替shared_ptr共享指针可以有效地避免环状引用问题。
2、由于unique_ptr在内存安全性、充当容器元素和支持动态数组方面均优于auto_ptr,因此C++11中使用unique_ptr代替了auto_ptr。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值