智能指针剖析(下)boost::shared_ptr&其他
1. boost::shared_ptr
前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点。由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不了了,如此我们再引入一个智能指针,专门用于处理复制,参数传递的情况,这便是如下的boost::shared_ptr。
boost::shared_ptr 也属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。在上面我们看到 boost::scoped_ptr 独享所有权,不允许赋值、拷贝,boost::shared_ptr 是专门用于共享所有权的,由于要共享所有权,其在内部使用了引用计数。boost::shared_ptr 也是用于管理单个堆内存对象的。
这是比较完善的一个智能指针,他是通过指针保持某个对象的共享拥有权的智能指针。若干个shared_ptr对象可以拥有同一个对象,该对象通过维护一个引用计数,记录有多少个shared_ptr指针指向该对象,最后一个指向该对象的shared_ptr被销毁或重置时,即引用计数变为0时,该对象被销毁。销毁对象时使用的是delete表达式或是在构造shared_ptr时传入的自定义删除器(delete),这后面会有详细讲解,但是shared_ptr指针同样拥有缺陷,那就是循环引用,和线程安全问题,这也在后面讲解。先来模拟实现一下shared_ptr指针。
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
,_count(new int()){
if (_ptr != NULL) {
++(*_count);
}
}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
,_count(sp._count){
if (_ptr != NULL) {
++(*_count);
}
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
if (this != &sp) { //排除对象本身自己给自己赋值
if (--(*_count) <= ) {
delete[]_ptr;
delete[]_count;
}
else //指向同一个对象的指针互相赋值
{ }
_ptr = sp._ptr;
_count = sp._count;
*(_count)++;
}
return *this;
}
~SharedPtr() {
if (--(*count) == ) {
delete[] _ptr;
delete[] _count;
}
}
T& operator*(){
return *_ptr;
}
T* operator->() {
return _ptr;
}
bool operator ==(const SharedPtr<T>& sp) {
return (_ptr == sp._ptr);
}
bool operator !=(const SharedPtr<T>& sp) {
return (_ptr != sp._ptr);
}
51 int UseCount() {
52 return *_count;
53 }
private:
T* _ptr;
int* _count;
};
1.1 问题1:线程安全
因为使用引用计数值位判定指标,所以在多线程的环境下是不安全的。会因线程调用的先后顺序不同导致错误产生。对于这种问题,解决方法一般是加锁,对引用计数进行加锁,保证操作是互斥的。
1.2 问题2:循环引用
针对循环引用,从实际的例子来大分析问题,以便能更好的理解。看下面代码:
struct ListNode
{
int _data;
SharedPtr<ListNode> _next;
SharedPtr<ListNode> _prev; ListNode(int x)
:_data(x), _next(NULL), _prev(NULL)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
}; void test()
{
SharedPtr<ListNode> A(new ListNode());
SharedPtr<ListNode> B(new ListNode()); if (A && B) {
A->_next = B;
B->_prev = A;
} cout << "A._count:" << A.UseCount() << endl;
cout << "B._count:" << B.UseCount() << endl;
} int main()
{
test();
getchar();
return ;
}
输出结果:
从结果可以看出两个节点的引用计数都是2,且一直没有调用析构函数,这将造成内存泄漏,下面我将图解原理:
而要解决循环引用的问题,就需要用boost库的另一个智能指针,即boost::weak_ptr。后面在详细讲解。
1.3 定制删除器(仿函数)
经上面分析,我们可以看到,上面的指针不能用于文件的关闭,也不能用于管理malloc和new[]开辟的动态内存的释放,所以我们可以运用仿函数来定制删除器。如下:
template<class T>
struct DeleteArray //用于new[]开辟的动态内存释放
{
void operator()(T* ptr)
{
cout << "A" << endl;
delete[] ptr;
}
};
struct Fclose //用于文件关闭
{
void operator()(FILE* ptr)
{
cout << "B" << endl; fclose(ptr);
}
};
template<class T>
struct Free //用于malloc开辟的动态内存的释放
{
void operator()(T* ptr)
{
cout << "C" << endl;
free(ptr);
}
};
int main()
{
shared_ptr<string> ap1(new string[], DeleteArray<string>());
shared_ptr<FILE> ap2(fopen("test.txt", "w"),Fclose());
shared_ptr<int> ap3((int*)malloc(sizeof(int)), Free<int>());
return ;
}
2. boost::weak_ptr
boost::weak_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。
在讲 boost::weak_ptr 之前,让我们先回顾一下前面讲解的内容。似乎boost::scoped_ptr、boost::shared_ptr 这两个智能指针就可以解决所有单个对象内存的管理了,这儿还多出一个 boost::weak_ptr,是否还有某些情况我们没纳入考虑呢?答案是有。首先 boost::weak_ptr 是专门为 boost::shared_ptr 而准备的。有时候,我们只关心能否使用对象,并不关心内部的引用计数。boost::weak_ptr 是 boost::shared_ptr 的观察者(Observer)对象,观察者意味着 boost::weak_ptr 只对 boost::shared_ptr 进行引用,而不改变其引用计数,当被观察的 boost::shared_ptr 失效后,相应的 boost::weak_ptr 也相应失效。weak_ptr其实是一个辅助性的智能指针,结合shared_ptr指针使用,它的本质就是弱引用,并不增加引用计数值。他没有实现->和*运算符的重载,所以不能直接用它访问对象。针对循环引用这个问题,就是因为不会引起引用计数值的改变,所以我们可以将_next和_prev定义为weak_ptr指针,这样就很好地解决了循环引用的问题。
struct ListNode
{
int _data;
weak_ptr<ListNode> _next; //定义为weak_ptr指针
weak_ptr<ListNode> _prev; ListNode(int x)
:_data(x), _next(NULL), _prev(NULL)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
其实 boost::weak_ptr 主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr,用于指向子类的boost::shared_ptr,这样基类仅仅观察自己的 boost::weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 boost::shared_ptr 的引用计数,用以降低复杂度,更好的管理对象。
boost库剩余的两个指针:auto_arr和shared_arr.这两个都是管理数组的,因为之前几个指针的析构函数中都是delete,不能对数组进行释放,所以我们自己定制删除器,而这两个指针就是专门管理指针的。下面也来模拟实现一下。
3. 模拟实现auto_arr
template<class T>
class AutoArr
{
public:
AutoArr(T* ptr = NULL)
:_ptr(ptr)
{} ~AutoArr()
{
delete[] _ptr;
} AutoArr(const AutoArr<T>& s)
{
_ptr = s._ptr;
s._ptr = NULL;
} AutoArr<T>& operator=(const AutoArr<T>& s)
{
if (this != &s)
{
_ptr = s._ptr;
s._ptr = NULL;
}
return *this;
} T& operator[](size_t pos)
{
if (_ptr == NULL)
{
throw a;
}
return *(_ptr+pos);
} void set(T* ptr)
{
int i = ;
while (*(ptr + i))
{
*(_ptr + i) = *(ptr + i);
i++;
}
} protected:
T* ptr;
};
4. 模拟实现shared_arr
template<class T> class SharedArr
{
public:
SharedArr(T* ptr = NULL)
:_ptr(ptr),_count(new int())
{
(*_count)++;
} ~SharedArr()
{
delete[] _ptr;
} SharedArr(const SharedArr<T>& s)
{
_ptr = s._ptr;
(*_count)++;
} SharedArr<T>& operator=(const SharedArr<T>& s)
{
if (this != &s)
{
if (--(*_count) <= )
{
delete _ptr;
delete _count;
}
else
{ }
_ptr = s._ptr;
_count = s._count;
(*_count)++;
}
} T& operator[](size_t pos)
{
if (_ptr == NULL)
{
throw ;
}
return *(_ptr + pos);
} protected:
T* _ptr;
int* _count;
};
5. 如何选择智能指针?
在掌握了这几种智能指针后,大家可能会想另一个问题:在实际应用中,应使用哪种智能指针呢?下面给出几个使用指南。
(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
- 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
- 两个对象都包含指向第三个对象的指针;
- STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。例如,可在程序中使用类似于下面的代码段。
unique_ptr<int> make_int(int n)
{
return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int> &p1)
{
cout << *a << ' ';
}
int main()
{
...
vector<unique_ptr<int> > vp(size);
for(int i = ; i < vp.size(); i++)
vp[i] = make_int(rand() % ); // copy temporary unique_ptr
vp.push_back(make_int(rand() % )); // ok because arg is temporary
for_each(vp.begin(), vp.end(), show); // use for_each()
...
}
其中push_back调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。
在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:
unique_ptr<int> pup(make_int(rand() % )); // ok
shared_ptr<int> spp(pup); // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % )); // ok
模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。
在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果你的编译器没有unique_ptr,可考虑使用Boost库提供的scoped_ptr,它与unique_ptr类似。
智能指针剖析(下)boost::shared_ptr&其他的更多相关文章
- C++智能指针剖析(下)boost::shared_ptr&其他
1. boost::shared_ptr 前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点.由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不 ...
- c++智能指针(unique_ptr 、shared_ptr、weak_ptr、auto_ptr)
一.前序 什么是智能指针? ——是一个类,用来存储指针(指向动态分配对象也就是堆中对象的的指针). c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写 ...
- 智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...
- C++智能指针剖析(上)std::auto_ptr与boost::scoped_ptr
1. 引入 C++语言中的动态内存分配没有自动回收机制,动态开辟的空间需要用户自己来维护,在出函数作用域或者程序正常退出前必须释放掉. 即程序员每次 new 出来的内存都要手动 delete,否则会造 ...
- c++智能指针的使用,shared_ptr,unique_ptr,weak_ptr
c++智能指针的使用 官方参考 普通指针的烦恼:内存泄漏,多次释放,提前释放 智能指针 负责自动释放所指向的对象. 三种智能指针 shared_ptr,unique_ptr,weak_ptr: 将sh ...
- 聊聊 C++ 中的几种智能指针 (下)
一:背景 上一篇我们聊到了C++ 的 auto_ptr ,有朋友说已经在 C++ 17 中被弃用了,感谢朋友提醒,今天我们来聊一下 C++ 11 中引入的几个智能指针. unique_ptr shar ...
- C++11智能指针(unique_ptr、shared_ptr、weak_ptr)(转)
原文地址:https://blog.csdn.net/king_way/article/details/95536938
- C++ auto_ptr智能指针的用法
C++中指针申请和释放内存通常采用的方式是new和delete.然而标准C++中还有一个强大的模版类就是auto_ptr,它可以在你不用的时候自动帮你释放内存.下面简单说一下用法. 用法一: std: ...
- 智能指针 shared_ptr 解析
近期正在进行<Effective C++>的第二遍阅读,书里面多个条款涉及到了shared_ptr智能指针,介绍的太分散,学习起来麻烦.写篇blog整理一下. LinJM @HQU s ...
随机推荐
- jmeter分布式压测
stop.sh需要跑Jmeter的服务器上安装Jmeteryum install lrzsz 安装rz.sz命令rz jemter的压缩包 拷贝到/usr/local/tools下面unzip apa ...
- Nginx编译参数
configure arguments: --with-cc-opt='-g -O2 -fPIE -fstack-protector //设置额外的参数将被添加到CFLAGS变量.(FreeBSD或者 ...
- React+ES6+Webpack环境配置
转自http://www.cnblogs.com/chenziyu-blog/p/5675086.html 参考http://www.tuicool.com/articles/BrAVv2y Reac ...
- Android中那些有你不知道的事
在安卓开发中,总有那么一些看似简单,实则绊脚的难题,等你去探索,等你去解决,也许你已经遇见了解决了,也许你还没碰上,写下这篇总结,希望能帮助那行即将遇到的朋友,快速解决这些小问题! 一.activit ...
- ArcGIS API for JavaScript 4.2学习笔记[20] 使用参数查询要素(油井和地震关系)
这个例子相当复杂.我先简单说说这个例子是干啥的. 在UI上,提供了一个下拉框.两个滑动杆,以确定三个参数,使用这三个参数进行空间查询.这个例子就颇带空间查询的意思了. 这个例子是干嘛的呢?第一个参数是 ...
- 某直播App问题分析
某直播App问题分析 一. 出现问题 观看自己开播的直播间,经常出现卡顿,而且画面一卡6,7s,重新播放时会出现跳帧,卡顿频率也较高,导致该App可用性极低. 二. 分析 1. 直播架构分析 根据lo ...
- 关于阿里图标库Iconfont生成图标的三种使用方式(fontclass/unicode/symbol)
1.附阿里图标库链接:http://www.iconfont.cn/ 2.登录阿里图标库以后,搜索我们需要的图标,将其加入购物车,如图3.将我们需要的图标全部挑选完毕以后,点击购物车图标4.这时候右侧 ...
- 读书笔记 effective c++ Item 47 使用traits class表示类型信息
STL主要由为容器,迭代器和算法创建的模板组成,但是也有一些功能模板.其中之一叫做advance.Advance将一个指定的迭代器移动指定的距离: template<typename IterT ...
- require.js入门
小颖目前所在的公司在用require.js,小颖一只说要写个小demo,今天抽空把自己写的小demo分享出来,希望对初学者有一些帮助,嘻嘻 学习资料: CSDN上的一篇文章:使用RequireJS优化 ...
- Linux查看网络端口
简单的总结一下前段时间学习Linux的成果 查看 TCP 22 端口是否打开1.列出所有端口:[root@Demon proc]# netstat -ntlpActive Internet conne ...