常用的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被c++11弃用
std::auto_ptr
例如auto_ptr<A> ptest(new A());
ptest.get()
返回一个原始指针(注意是".")
ptest.release()
只是把智能指针赋值为空,但是它原来指向的内存并没有被释放,相当于它只是释放了对资源的所有权
ptest.reset()
重新绑定指向的对象,可以使用它提前释放资源
判断智能指针是否为空不能用if(ptest == NULL)
,应该用if(ptest.get() == NULL)
std::unique_ptr
unique_ptr是取代C++98的auto_ptr的产物,它是个独享所有权的智能指针,提供了严格意义上的所有权,包括:
1.拥有它指向的对象
2.无法进行拷贝构造,无法进行复制赋值操作.即2个unique_ptr无法指向同一个对象,但可以进行移动构造和移动赋值.
3.保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象.
unique_ptr可实现以下功能:
1.为动态申请内存提供异常安全
2.将动态申请的内存所有权传递给某函数
3.从某个函数返回动态申请内存的所有权
4.在容器中保存指针
5.auto_ptr 应该具有的功能
它和auto_ptr用法很相似,不过不能使用两个智能指针赋值,应使用std::move;而且它可直接用if(ptest==NULL)
判断空指针;另外要注意,当它当做参数传递给函数时(值传递,引用不用)也要用std::move,如foo(std::move(ptest)).它还新增swap成员函数用于交换2个智能指针的值.
std::shared_ptr
从名字可看出它看被多个指针共享,调用release()释放当前资源所有权,计数减一.
struct A;
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<A> p2(new A);
使用refcount技术实现实现的,因此内部有一个计数器(控制块,用于管理数据)和一直指针,指向数据.因此在执行std::shared_ptr p2(new A)
的时候,首先会申请数据的内存,然后申请内控制块,因此是两次内存申请,而std::make_shared()
则是只执行一次内存申请,将数据和控制块的申请放到一起.那么问题来了,二者会带来什么不同的影响吗?
异常安全
考虑如下代码:
void f(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs){...}
f(std::shared_ptr<Lhs>(new Lhs()),
std::shared_ptr<Rhs>(new Rhs()));
C++允许参数在计算的时候打乱顺序(不同调用约定入栈顺序就不一样),例如其中一种顺序为:new Lhs=>new Rhs=>std::shared_ptr=>std::shared_ptr
假设此时,第二步异常,那么Lhs就会没处释放,造成内存泄漏,因为指针没能马上传给std::shared_ptr.因此,一个可能的解决方法:
auto lhs = std::shared_ptr<Lhs>(new Lhs());
auto rhs = std::shared_ptr<Rhs>(new Rhs());
f(lhs, rhs);
而一个比较好的方法是使用std::make_shared:
f(std::make_shared<Lhs>(),
std::make_shared<Rhs>());
那么,make_shared就完美了吗?
make_shared的缺点
因为make_shared只申请一次内存,因此控制块和数据块在一起;
只有当控制块中不再使用时,内存才会释放;但是weak_ptr却使得控制块一直在使用.
std::weak_ptr
weak_ptr用来协助shared_ptr工作,可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用计数(ref count)的变化,也没重载*和->,但可以通过lock获得一个可用的shared_ptr对象,其主要用途有2个:
- 用来记录对象是否存在了
- 用来解决shared_ptr环形依赖问题(解决shared_ptr相互引用导致的死锁问题)
下面是存在环形依赖的代码:
class B;
class A
{
public:
shared_ptr<B> pb_;
~A() {cout<<"A delete\n";}
};
class B
{
public:
shared_ptr<A> pa_;
~B(){cout<<"B delete\n";}
};
void fun()
{
shared_ptr<B> pb(new B());
shared_ptr<A> pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<<pb.use_count()<<endl;
cout<<pa.use_count()<<endl;
}
int main()
{
fun();
return 0;
}
可以看到pa,pb相互引用,两个资源计数为2,跳出函数2个资源引用计数会减一.但二者引用计数还是1,导致资源没被释放,只要把其中一个改为weak_ptr就可以了.例如把A中的shared_ptr<B> pb_;
改为weak_ptr<B> pb_;
这样资源B引用计数开始就是1,pb析构B释放的同时A计数减一,pa析构计数再次减一,A也得到释放.
我们不能通过weak_ptr直接访问对象的方法,不如B中有一个方法print(),我们不能这样pa->pb_->print()
,因为pb_是一个weak_ptr,应先把它转为shared_ptr.如:sahred_ptr<B> p = pa->pb_.lock(); p->print();