原文: http://blog.csdn.net/coolmeme/article/details/43266319

笔记:

weak_ptr 是一种可以像share_ptr一样使用,但是不own指针的结构, 引用计数不增加。

weak_ptr 与裸指针的区别,weak ptr能知道对象是不是不存在了,裸指针不知道。

应用场景1:观察者模式,subject本身不own observer的生命周期,可以保存oberver的指针到weak_ptr

应用场景2:互相引用的对象,如果用share ptr,永远也不能销毁对象。

weak ptr只能从share ptr 构造,使用也只能转成share ptr使用,当转成share ptr时,若原始对象已经销毁,则share ptr为空,以此来判断原始对象是否可用。

c++11 条款20:使用std::weak_ptr作为一个类似std::share_ptr但却能悬浮的指针

2015-01-29 17:46 3364人阅读 评论(0) 收藏 举报

条款20:使用std::weak_ptr作为一个类似std::share_ptr但却能悬浮的指针

有一个矛盾,一个灵巧指针可以像std::shared_ptr (见条款 19)一样方便,但又不参与管理被指对象的所有权。换句话说,需要一个像std::shared_ptr但又不影响对象引用计数的指针。这类指针会有一个std::shared_ptr没有的问题:被指的对象有可能已经被销毁。一个良好的灵巧指针应该能处理这种情况,通过跟踪什么时候指针会悬浮,比如在被指对象不复存在的时候。这正是std::weak_ptr这类型灵巧指针所能做到的。

你可能疑惑std::weak_ptr能有什么用处,在你看了std::weak_ptr的API后可能更疑惑。它看上去根本不灵巧。std::weak_ptr不能解引用,也不能检查是否为空。这是因为std::weak_ptr不能作为一个独立的灵巧指针,它是作为std::shared_ptr的延伸。

指针生成的时刻就决定了这种关系。std::weak_ptr一般是通过std::shared_ptr来构造的。当std::shared_ptr初始化std::weak_ptr时,std::weak_ptr就指向了相同的地方,但它不改变所指对象的引用计数。

auto spw =                                   // after spw is constructed,
    std::make_shared<Widget>(); // the pointed-to Widget's

// ref count (RC) is 1. (See
                                                    // Item 21 for info on
                                                    // std::make_shared.)

std::weak_ptr<Widget> wpw(spw); // wpw points to same Widget
                                                        // as spw. RC remains 1

spw = nullptr;  // RC goes to 0, and the
                       // Widget is destroyed.
                       // wpw now dangles

std::weak_ptr成为悬浮指针也被称作过期。你可以直接检测,

if (wpw.expired()) … // if wpw doesn't point
                                 // to an object…

但是经常期望的是检查一个std::weak_ptr是否已经过期,以及是否不能访问访问做指向的对象。这个比较难做到。因为std::weak_ptr缺乏解引用操作,没法写这样的代码。即使有,把检查和解引用分隔开来也会引起竞争冲突:在调用过期操作(expired)和解引用之间。另一个线程会重新分配或者删除指向对象的最后一个std::shared_ptr,这会引起的对象被销毁,于是你的解引用会产生未定义行为。

你所需要的是一个原子操作来检查std::weak_ptr是否过期,如果没过期则提供对所指对象的访问。可以通过从std::weak_ptr构造std::shared_ptr来实现上述操作。这个操作有两个形式,取决于假如你从std::weak_ptr来构造std::shared_ptr时std::weak_ptr已经失效你期望发生什么情况。一种形式是std::weak_ptr::lock,它返回一个std::shared_ptr。如果std::weak_ptr失效,则std::shared_ptr为空:

std::shared_ptr<Widget> spw1 = wpw.lock(); // if wpw's expired,
                                                                        // spw1 is null
auto spw2 = wpw.lock(); // same as above, 
                                       // but uses auto

另一种形式是把std::weak_ptr作为参数来构造std::shared_ptr。这样,如果std::weak_ptr失效的话,则会抛异常:

std::shared_ptr<Widget> spw3(wpw); // if wpw's expired,
                                                            // throw std::bad_weak_ptr

可能你还是很疑惑std::weak_ptr怎样使用呢。设想一个工厂函数,基于唯一ID来创建一些指向只读对象的灵巧指针。根据条款18对工厂函数返回类型的建议,应该返回一个 std::unique_ptr:

std::unique_ptr<const Widget> loadWidget(WidgetID id);

假如loadWidget是一个昂贵的调用(比如因为涉及到文件或数据库io)而且经常会被相同的ID重复调用,一个合理的优化是写一个函数做loadWidget的工作,并且缓存结果。然而保持每一个请求过的Widget在缓存中可能会引起性能问题,所以另一个优化就是在Widget不再使用时删除之。

对这个工厂函数来说,返回一个std::unique_ptr并不是最合适的。调用者获得灵巧指针并缓存下来,调用者决定了这些对象的生存期,但是缓存也需要一个指向这些对象的指针。缓存的指针需要能够检测什么时候是悬浮的,因为工厂函数的使用者不在使用这些指针时,对象会被销毁,这样相关的cache项就会变成悬浮指针。于是缓存的指针应该是std::weak_ptr---这样可以检测到什么时候悬浮。那么这意味着工厂的返回值应该是std::shared_ptr,因为只有对象的生存期由std::shared_ptr管理时,std::weak_ptr才可以检测到何时悬浮。

这里有个快速但不好的loadWidget缓存实现方案:
std::shared_ptr<const Widget> fastLoadWidget(WidgetID id)
{
static std::unordered_map<WidgetID,
                                          std::weak_ptr<const Widget>> cache;
auto objPtr = cache[id].lock(); // objPtr is std::shared_ptr
                                                // to cached object (or null
                                                // if object's not in cache)

if (!objPtr) {                              // if not in cache,
    objPtr = loadWidget(id);      // load it
    cache[id] = objPtr;               // cache it
}

return objPtr;
}

这个实现使用了C++11中的hash table容器(std::unorderer_map),尽管没有显示出WidgetID的hash计算和比较相等的函数。

fastLoadWidget 的实现忽略了缓存中的过期的std::weak_ptr会不断积累,因为相关联的Widget可能不再使用(因此会被销毁)。这个实现可以被改善,而不是花时间去深入到std::weak_ptr中去观察,让我们考虑第二种情况:观察者模式。改模式的主要部件是主题(Subjects,状态可以改变的对象)和观察者(Observers,状态改变发生后被通知的对象)。多数实现中,每个subject包含了一个数据成员,保持着指向observer的指针,这样很容易在subject发生状态改变时发通知。subject没有兴趣控制它们的observer的生命周期(不关心何时它们被销毁),但他要确认一个observer是否被销毁,这样避免去访问。一个合理的设计就是每个subject保存一个容器,容器里放着每个observer的std::weak_ptr,这样在subject在使用前就可以检查observer指针是否是悬浮的。

最后再举一个使用std::weak_ptr的例子,考虑一个数据结构,里面包含了A,B,C3个对象,A和C共享B的所有权,因此保持了一个B的std::shared_ptr:

假设有一个指针从B回指向A,那么这个指针应该用什么类型的指针呢?

有三中选择:

1.一个原始指针。 这种情况下,如果A被销毁,C依然指向B,B保存着指向A的指针,但是已经是悬浮指针了。B却检测不出来,所以B有可能去解引用这个悬浮指针,结果就是为定义的行为。

2.一个std::shared_ptr。这种情况下,A和B互相保存着一个std::shared_ptr,结果这个环路(A指向B,B指向A)组织了A和B被销毁。即使程序的其他数据已经不再访问A和B,它们两者都互相保存着对方一个引用计数。这样,A和B就内存泄漏了,实用中,程序将不可能访问到A和B,而它们的资源也将不会被重新使用。

3.一个std::weak_ptr。这将避免上述两个问题。假如A被销毁,那么B的回指指针将会悬浮,但是B可以检测到。进一步说,A和B虽然都互相指想彼此,但是B的指针不影响A的引用计数,所以当std::shared_ptr不再指向A时,并不能阻止A被销毁。

使用std::weak_ptr无疑是最好的选择。然而用std::weak_ptr来打破std::shared_ptr引起的循环并不那么常见,所以这个方法也不值一提。严格来讲层级数据结构,比如tree,孩子结点一般都只被父节点拥有,当父节点被销毁后,所有的孩子结点也都应该被销毁。这样,从父节点到子节点的链接可以用std::unique_ptr来表示,而反过来从子节点到父节点的指针可以用原始指针来实现,因为子节点的生命周期不会比父节点的更长,所以不会出现子节点去解引用一个父节点的悬浮指针的情况。

当然并非所有的基于指针的数据结构都是严格的层级关系的。比如像缓存的情况以及观察者列表的实现,使用std::weak_ptr就非常好。

从效率的角度来看,std::weak_ptr和std::shared_ptr几乎一致。它们尺寸相同,都使用了控制块(见条款19),其构造,析构,赋值都涉及了对引用计数的原子操作。你可能会吃惊,因为我在本条款开始提到了std::weak_ptr不参与引用计数的操作。其实那不是我写的,我写的是std::weak_ptr不涉及对象的共享所有权,因此不影响对象的引用计数。实际山控制块里面有第二个引用计数,std::weak_ptr操作的就是这第二个引用计数。更详细的描述见条款21。

需要记住的事情

1.使用std::weak_ptr来指向可能悬浮的std::shared_ptr一样的指针。

2.可能使用std::weak_ptr的情况包括缓存,观察模式中的观察者列表,以及防止std::shared_ptr环路。

std:weak_ptr相关推荐

  1. 从源码看std::weak_ptr

    1. 序 本篇文章来讲解一下weak_ptr, weak_ptr一般也都是和shared_ptr同时存在的,相当于对对象的弱化版引用.我们首先从源码的角度来讲解下weak_ptr,然后再讲一下weak ...

  2. C++智能指针:weak_ptr实现详解

    文章目录 weak_ptr描述 声明 作用 原理实现 函数成员使用 总结 weak_ptr描述 声明 头文件:<memory> 模版类:template <class T> c ...

  3. C++11中weak_ptr的使用

    在C++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化:delete,接受一个动态对象的指针,销毁该对象,并释放与之 ...

  4. boost智能指针之shared_ptr,scoped_ptr,intrusive_ptr,weak_ptr源码简析

    boost库实现了各种智能指针,基本上都纳入了c++11标准中,boost库的smart_ptr目录下就是各种指针的实现了: 1.shared_ptr template<class T> ...

  5. 智能指针weak_ptr

    weak_ptr这个指针天生一副小弟的模样,也是在C++11的时候引入的标准库,它的出现完全是为了弥补它老大shared_ptr天生有缺陷的问题. 相比于上一代的智能指针auto_ptr来说,新进老大 ...

  6. C++ 11 创建和使用共享 weak_ptr

    1.为什么需要weak_ptr? 在正式介绍weak_ptr之前,我们先来回忆一下shared_ptr的一些知识.我们知道shared_ptr是采用引用计数的智能指针,多个shared_ptr实例可以 ...

  7. [C++11]弱引用智能指针weak_ptr初始化和相关的操作函数

    弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针.std::weak_ptr 没有重载操作符 * 和 ->,因为它 ...

  8. std::make_unique<T>和std::make_shared<T>

    更建议使用:std::make_unique<T>构造unique_ptr对象:std::make_shared<T>构造shared_ptr对象 std::make_shar ...

  9. C++ std::tr1::shared_ptr使用说明

    1. 介绍 shared_ptr 是通过指针保持某个对象的共享拥有权的智能指针. 若干个 shared_ptr 对象能够拥有同一个对象:最后一个指向该对象的 shared_ptr 被销毁或重置时.该对 ...

最新文章

  1. 自动驾驶十字路口解决方案
  2. Notification和KVO有什么不同
  3. php显示页面查询次数和耗时,wordpress显示页面查询次数、页面执行时间
  4. CSS你可能还不知道的一些知识点
  5. php 数组 构造为树,从PHP中的平面数组构建树
  6. Android开发笔记(十一)自定义视图的构造方法
  7. 日常的SQL 语句使用
  8. python操作之更新数据库中某个字段的数据
  9. BLUE引擎检查放入装备的名称全名脚本
  10. 电脑配置PC2022年版(4000元左右)详细配置表——(专业数据)
  11. 最新中国大陆行政区域划分
  12. 南京国际动漫游戏博览会 一日游(10-5)
  13. Microsoft Excel 教程:如何在 Excel 中创建图表?
  14. php多虚拟主机配置
  15. 读书笔记2014第8本:《追寻生命的意义》
  16. 【无机纳米材料科研制图——Visio 0309】使用任意多边形工具描边出小鼠简图
  17. [OHIF-Viewers]医疗数字阅片-医学影像-Cornerstone
  18. 我工作上常用的--测试用例文档模板
  19. windows10 如何使用 debug
  20. 根据结构体数组中某一数据项对结构体数组排序

热门文章

  1. 调戏木马病毒的正确姿势——下
  2. java第二课——IDEA介绍
  3. 什么是畏缩型性格?如何改变畏缩型性格?
  4. TDA4862G 经过优化,完全兼容世界标准,能够实现极其紧凑并且极具成本效益的 PFC 解决方案
  5. [VOT12](2017CVPR) CSR-DCF: Discriminative Correlation Filter Tracker with Channel and Spatial
  6. 多功能检测按键 单按 长按 多个按键 响应方式
  7. Ruff Chain韩国Meetup回顾:物联网因区块链而改变
  8. 武警二路擒敌拳16式
  9. pcd点云地图转换为八叉树ot地图
  10. 数据库建表时, 没有成功创建表 No migrations to apply报错原因和解决方法