转载请注明出处,谢谢.
智能指针可以通过引用计数的策略自动销毁对象,其作用不仅仅是偷了一点点懒,而是可以解决一些大型工程难题(尤其是多线程环境下对象的销毁问题).最近开始看<<Linux多线程服务端编程 使用muduo c++网络库>>(陈硕 著),感觉以上来它就讲了我写项目正在遇到的问题,提到了使用智能指针解决问题.
#include <memory>
一.shared_ptr<Type>
强引用.一个强引用类型的智能指针能增加目标对象的引用计数.
先举个栗子大概看一下怎么用:
#include<bits/stdc++.h>
using namespace std;
struct node{
int v;
node()
{
cout<<"node.create"<<endl;
}
node(int a):node()
{
v=a;
}
~node()
{
cout<<"node.destroy"<<endl;
}
};
int main()
{
{
shared_ptr<node>p(new node);
p->v=1;
cout<< p.get()->v <<endl;
}
cout<<"end"<<endl;
}
运行一下:
文件名单词拼错了,不要在意这些细节.
注意看,对象被自动销毁了.
first.先看看怎么构造
第一种方法,可以跟上述例子一样:
使用shared_ptr(Type *p)构造方法:
shared_ptr<node>p(new node);
第二种,用make_shared<Type>(...)创建一个共享指针实例; 参数的填写方法取决于该对象的构造方法.比如:
shared_ptr<node>p=make_shared<node>(); //调用 node()构造方法
shared_ptr<node>q=make_shared<node>(5); //调用 node(int a)构造方法
当然也可以用万能的auto简化书写 个人觉得这里auto特好用(反正共享指针IDE的补全也是很操蛋)
auto p=make_shared<node>();
second.看看成员函数
摆一下成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr<int> sp(new int(1)); sp 与 sp.get()是等价的
(成员函数的介绍来自阿Hai的博客)
然后大概举例子看看这些函数怎么用:
use_count, reset和unique:
shared_ptr<node> p=make_shared<node>(5);
cout<<"use_count="<<p.use_count()<<endl;
cout<<"unique="<<p.unique()<<endl;
auto p2=p;
cout<<"use_count="<<p.use_count()<<endl;
cout<<"unique="<<p.unique()<<endl;
我们发现赋值之后引用计数+1了,然后p指针也不是独占对象了.而如果加一句reset:
{
shared_ptr<node> p=make_shared<node>(5);
cout<<"use_count="<<p.use_count()<<endl;
cout<<"unique="<<p.unique()<<endl;
auto p2=p;
p.reset();
cout<<"use_count="<<p.use_count()<<endl;
cout<<"unique="<<p.unique()<<endl;
}
cout<<"end"<<endl;
然后就可以释放掉引用:
如果是取reset之后的指针,则use_count=0,unique=0;(亲测)
get函数
简介里很清楚了,第一个例子里已经演示过了就不重复了.
swap函数
swap就是交换指向的对象了,如:
{
shared_ptr<node> p=make_shared<node>(5);
shared_ptr<node> p2=make_shared<node>(3);
cout<< p->v <<endl;
cout<< p2->v <<endl;
p.swap(p2);
cout<< p->v <<endl;
cout<< p2->v <<endl;
}
cout<<"end"<<endl;
third.自定义释放器
我们可以给智能指针提供一个释放对象的函数,这样就可以释放掉对象之后再做一些收尾工作,比如:
我们在销毁掉node类的一个示例后,跟它说声"再见":
void sayGoodBye(node *obj)
{
delete obj;
cout<<"GoodBye"<<endl;
}
int main()
{
{
shared_ptr<node> p(new node(5),sayGoodBye);
}
cout<<"end"<<endl;
}
当然,也可以在销毁前说再见,具体怎么做可以在释放器中自己定义.
这个功能很实用.
fourth.小心循环引用
在此之前,提一句:智能指针的复制构造函数在传参的时候会使引用计数+1,参数销毁时-1.
举个循环引用的栗子:
#include<bits/stdc++.h>
using namespace std;
struct node{
int v;
shared_ptr<node>ptr;
void set(shared_ptr<node> &p) //no copy
{
ptr=p;
cout<<ptr.use_count()<<endl;
}
node()
{
cout<<"node.create"<<endl;
}
node(int a):node()
{
v=a;
}
~node()
{
cout<<"node.destroy"<<endl;
}
};
int main()
{
{
shared_ptr<node> p(new node(5));
shared_ptr<node> p1(new node(3));
p->set(p1);
p1->set(p);
}
cout<<"end"<<endl;
}
两个对象只被创建了,都没被销毁,造成了内存泄露.
解决方案就是在类成员中使用weak_ptr代替shared_ptr.至于weak_ptr是什么下面解释.
二.weak_ptr<Type>
weak_ptr(弱引用)是为了配合shared_ptr(强引用)而设计的.它不会增加对象的引用计数,但是可以判断该对象是否有效(有没有被销毁),以及提供对目标对象的访问.
成员函数
weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.
expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
use_count 返回与 shared_ptr 共享的对象的引用计数.
reset 将 weak_ptr 置空.
weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.
weak_ptr 在功能上类似于普通指针, 然而一个比较大的区别是, 弱引用能检测到所管理的对象是否已经被释放, 从而避免访问非法内存。
(成员函数的介绍来源于阿Hai的博客)
也就是说,weak_ptr完全是shared的一个助手,完全没有人权,只用来解决shared_ptr的循环引用问题.
上面循环引用的例子里,只需要把成员变量shared_ptr换成weak_ptr就可以了.不过我们为了看一下weak_ptr怎么用,给上面的例子里增加一丢丢操作.
改正后的代码:
#include<bits/stdc++.h>
using namespace std;
struct node{
int v;
weak_ptr<node>ptr;
void set(shared_ptr<node> &p) //no copy
{
ptr=p;
cout<<ptr.use_count()<<endl;
}
void printFrinedv()
{
shared_ptr<node>p=ptr.lock();
if(p) //if p.get() exists ?
{
cout<< "friend.v=" << p->v <<endl;
}
else
{
cout<<"friend has already been destroyed"<<endl;
}
}
node()
{
cout<<"node.create"<<endl;
}
node(int a):node()
{
v=a;
}
~node()
{
cout<<"node.destroy"<<endl;
}
};
int main()
{
{
shared_ptr<node> p(new node(5));
shared_ptr<node> p1(new node(3));
p->set(p1);
p1->set(p);
p->printFrinedv();
p1.reset();
p->printFrinedv();
}
cout<<"end"<<endl;
}
其中lock函数是原子操作.不需要额外加锁.