weak_ptr 弱智能指针

Effecvive Modern C++
使用std::weak_ptr 来代替可能空悬的 std:: shared_ptr 。
std::weak_ptr 可能的用武之地包括缓存,观察者列表,以及避免 std::shared_ptr 指针环路 。

文章目录

  • weak_ptr 弱智能指针
    • weak_ptr 观察者 —— 基本功能
    • weak_ptr解决循环引用问题 —— 引用对象,用weak_ptr
    • 线程安全的对象回调与析构 —— 弱回调
      • 替代方案

std: :weak_ptr 一般者是通过 std: : shared _ptr 来创建的。当使用 std: :shared_ptr 完成初始化 std::weak_ptr 的时刻,两者就指涉到了相同位置

弱智能指针weak_ptr区别于shared_ptr之处在于:

  • weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在
  • weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
  • weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock方法提升为shared_ptr强智能指针,才能访问资源

weak_ptr 观察者 —— 基本功能

weak_ptr内几个重要成员函数:

  1. 成员函数use_count() 观测资源引用计数

  2. 成员函数expired() 功能相当于 use_count()==0 表示被观测的资源(也就是shared_ptr的管理的资源)是否被销毁

  3. 成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 进而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr

class CTxxx {public:    CTxxx() {printf( "CTxxx cst\n" );}~CTxxx() {printf( "CTxxx dst\n" );
};int main() {std::shared_ptr<CTxxx> sp_ct(new CTxxx);std::weak_ptr<CTxxx> wk_ct = sp_ct;std::weak_ptr<CTxxx> wka1;{std::cout << "wk_ct.expired()=" << wk_ct.expired() << std::endl;std::shared_ptr<CTxxx> tmpP = wk_ct.lock();if (tmpP) {std::cout << "tmpP usecount=" << tmpP.use_count() << std::endl;} else {std::cout << "tmpP invalid" << std::endl;}std::shared_ptr<CTxxx> a1(new CTxxx);wka1 = (a1);}std::cout << "wka1.expired()=" << wka1.expired() << std::endl;std::cout << "wka1.lock()=" << wka1.lock() << std::endl;std::shared_ptr<CTxxx> cpySp = wka1.lock();if (cpySp) std::cout << "cpySp is ok" << std::endl;else std::cout << "cpySp is destroyed" << std::endl;return 1;
}

weak_ptr解决循环引用问题 —— 引用对象,用weak_ptr

请注意强弱智能指针的一个重要应用规则:定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr

class B; // 前置声明类B
class A
{public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }weak_ptr<B> _ptrb; // 指向B对象的弱智能指针。引用对象时,用弱智能指针
};
class B
{public:B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }weak_ptr<A> _ptra; // 指向A对象的弱智能指针。引用对象时,用弱智能指针
};
int main()
{// 定义对象时,用强智能指针shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1// A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变ptra->_ptrb = ptrb;// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变ptrb->_ptra = ptra;cout << ptra.use_count() << endl; // 打印结果:1cout << ptrb.use_count() << endl; // 打印结果:1/*出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和B对象的引用计数从1减到0,达到释放A和B的条件,因此new出来的A和B对象被析构掉,解决了“强智能指针的交叉引用(循环引用)问题”*/return 0;
}

线程安全的对象回调与析构 —— 弱回调

有时候我们需要“如果对象还活着,就调用它的成员函数,否则忽略之”的语意,就像Observable::notifyObservers()那样,我称之为“弱回调”。这也是可以实现的,利用weak_ptr,我们可以把weak_ptr绑到boost::function里,这样对象的生命期就不会被延长。然后在回调的时候先尝试提升为shared_ptr,如果提升成功,说明接受回调的对象还健在,那么就执行回调;如果提升失败,就不必劳神了。

muduo的源代码,该源码中对于智能指针的应用非常优秀,其中借助shared_ptr和weak_ptr解决了这样一个问题,多线程访问共享对象的线程安全问题,解释如下:线程A和线程B访问一个共享的对象,如果线程A正在析构这个对象的时候,线程B又要调用该共享对象的成员方法,此时可能线程A已经把对象析构完了,线程B再去访问该对象,就会发生不可预期的错误。

class Test
{public:// 构造Test对象,_ptr指向一块int堆内存,初始值是20Test() :_ptr(new int(20)) {cout << "Test()" << endl;}// 析构Test对象,释放_ptr指向的堆内存~Test(){delete _ptr;_ptr = nullptr;cout << "~Test()" << endl;}// 该show会在另外一个线程中被执行void show(){cout << *_ptr << endl;}
private:int *volatile _ptr;
};
void threadProc(weak_ptr<Test> pw) // 通过弱智能指针观察强智能指针
{// 睡眠两秒std::this_thread::sleep_for(std::chrono::seconds(2));/* 如果想访问对象的方法,先通过pw的lock方法进行提升操作,把weak_ptr提升为shared_ptr强智能指针,提升过程中,是通过检测它所观察的强智能指针保存的Test对象的引用计数,来判定Test对象是否存活,ps如果为nullptr,说明Test对象已经析构,不能再访问;如果ps!=nullptr,则可以正常访问Test对象的方法。*/shared_ptr<Test> ps = pw.lock();if (ps != nullptr){ps->show();}
}
int main()
{// 在堆上定义共享对象shared_ptr<Test> p(new Test);// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针std::thread t1(threadProc, weak_ptr<Test>(p));// 在main线程中析构Test共享对象// 等待子线程运行结束t1.join();return 0;
}

运行上面的代码,show方法可以打印出20,因为main线程调用了t1.join()方法等待子线程结束,此时pw通过lock提升为ps成功,见上面代码示例。

如果设置t1为分离线程,让main主线程结束,p智能指针析构,进而把Test对象析构,此时show方法已经不会被调用,因为在threadProc方法中,pw提升到ps时,lock方法判定Test对象已经析构,提升失败!main函数代码可以如下修改测试:

int main()
{// 在堆上定义共享对象shared_ptr<Test> p(new Test);// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针std::thread t1(threadProc, weak_ptr<Test>(p));// 在main线程中析构Test共享对象// 设置子线程分离t1.detach();return 0;
}

该main函数运行后,最终的threadProc中,show方法不会被执行到。以上是在多线程中访问共享对象时,对shared_ptr和weak_ptr的一个典型应用

替代方案

除了使用shared_ptr/weak_ptr,要想在C++里做到线程安全的对象回调与析构,可能的办法:

1.用一个全局的façade来代理Foo类型对象访问,所有的Foo对象回调和析构都通过这个façade来做,也就是把指针替换为objId/handle,每次要调用对象的成员函数的时候先check-out,用完之后再check-in16。
这样理论上能避免race condition,但是代价很大。因为要想把这个façade做成线程安全的,那么必然要用互斥锁。这样一来,从两个线程访问两个不同的Foo对象也会用到同一个锁,让本来能够并行执行的函数变成了串行执行,没能发挥多核的优势。当然,可以像Java的ConcurrentHashMap那样用多个buckets,每个bucket分别加锁,以降低contention。

weak_ptr 的几个应用场景 —— 观察者、解决循环引用、弱回调相关推荐

  1. 解决循环引用--弱引用weak_ptr

    循环引用:  引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象.一个简单的例子如下: class parent; class children;typedef sh ...

  2. 【C++】智能指针简述(五):解决循环引用的weak_ptr

    总结一下前文内容: 1.智能指针通过RAII方法来管理指针:构造对象时,完成资源初始化;析构对象时,对资源进行清理及汕尾. 2.auto_ptr,通过"转移所有权"来防止析构一块内 ...

  3. NSTimer解决循环引用常见方法

    1. NSTimer的使用 常见使用场景如下: - (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = [UIColo ...

  4. python垃圾回收机制为什么标记能解决循环引用问题_Python 垃圾回收机制和如何解决循环引用...

    引用计数:是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术, 当一个对象的引用被创建或者复制时,对象的引用计数加 1:当一个对象的引用被销毁时,对象的引用计数减 1:当对象的引用计数减少为 ...

  5. js深拷贝,解决循环引用

    概念 前提为拷贝类型为引用类型的情况下: 浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址 浅拷贝的方 ...

  6. php 解决循环引用,excel循环引用如何解决

    excel循环引用如何解决? 方法一 需要找到"excel选项",这个地方在Office 2003版本里的"工具"选项里,而在Office 2007版本里不好找 ...

  7. java中出现循环问题如何解决_java如何解决循环引用

    Excel 循环引用产生的原因及解决方法 来源:excel 格子社区 我们打开 ... (Garbage Collection Thread) , 来跟踪每一块分配出去的内存空间, Java 虚拟机 ...

  8. block为什么用copy以及如何解决循环引用

    在完成项目期间,不可避免的会使用到block,因为block有着比delegate和notification可读性更高,而且看起来代码也会很简洁.于是在目前的项目中大量的使用block. 之前给大家介 ...

  9. Flask-分开Models解决循环引用

    在之前我们测试中,所有语句都在同一个文件中,但随着项目越来越大,管理起来有所不便,所以将Models分离. 基本的文件结构如下 \-–app.py \-–models.py from flask im ...

最新文章

  1. 首席信息官利用AI提升自身地位的三种方法
  2. 漫画:为什么计算机用补码存储数据?
  3. C语言解析http请求表单内容
  4. python语言发明者 google_看看9种编程语言的发明者是怎么说的
  5. CCF201803-1 跳一跳
  6. python ConfigParser模块详解
  7. oracle数据库图书,基于oracle数据库,创建图书表(一)
  8. python入门经典100题-零基础学习Python开发练习100题实例(1)
  9. JSON数据写入和解析
  10. 【深度学习】你不了解的细节问题(四)
  11. 遍地是钱,为什么捡不到?
  12. 快捷键,总结一些实用高效的快捷键
  13. 互联网日报 | 1月27日 星期三 | 支付宝集五福活动2月1日开启;华为否认“出售手机业务”传闻;中国联通自有手机品牌发布...
  14. 基于BASYS3的VHDL交通灯控制器——有限状态机(FSM)
  15. 时空大数据面临的挑战与机遇
  16. Image Segmentation
  17. 毕业十年,唯有独立面对——记 贺利坚老师新书《逆袭大学——传给IT学子的正能量》
  18. Android Developer:合并清单文件
  19. 利用poi 读取excel通用工具类
  20. 【论文阅读】Oriented R-CNN for Object Detection

热门文章

  1. 网络流之最大流 EK/Dinic/Isap算法 学习笔记
  2. mysql连续左连接(left join)和多层从属子表的查询
  3. Uipath如何截图粘贴到Excel
  4. V-RAY NEXT FOR MAYA 实用指南助您掌握 V-Ray 的关键技术
  5. android 原生分享界面_Android 很好用的「桌面启动器」更新大版本,变得更易上手了...
  6. hud2504 又见GCD
  7. 详探TextRange对象--查找与选择
  8. 织梦的网站地图怎么做html,织梦(dedecms)网站地图改变生成目录的方法
  9. .[转] 叠衣服-系鞋带-打领带(实用)
  10. 理财入门:复利篇与科学记账法