Effective Modern C++ Item 20 对于类似std::shared_ptr但有可能悬空的指针,使用std::weak_ptr

2023-10-29

如果需要某种智能指针能够像std::shared_ptr一样方便,但又无需参与管理所指涉到的对象的共享所有权的话。就很好适合用std::weak_ptr

但这样的功能同样会带来一个问题。这种指针需要处理一个对std::shared_ptr而言不是问题的问题:所指涉的对象有可能已经被析构。而std::weak_ptr的确是可以判断所指向对象是否还存在。

std::weak_ptr的用途

在看完std::weak_ptr的API后,你可能会困惑,这东西不能取地址,也不能检查是否为空。这东西到底有什么用呢。其实这个东西需要配合std::shared_ptr使用,相当于std::shared_ptr的一种扩充。

用途示例,一般std::weak_ptrstd::shared_ptr创建:

auto spw =                  //完成spw构造后,指涉到Widget的引用计数为1
std::make_shared<Widget>();

std::weak_ptr<Widget> wpw(spw); //wpw和spw指向同一个Widget,引用计数保持为1

...

spw = nullptr;              // 引用计数为0,Widget对象被析构,wpw悬空

if (wpw.expired()) ...      // 若wpw不再指涉到任何对象,可以用来判断悬空

std::weak_ptr的用法

通常的一个使用场景是,判断一个std::weak_ptr是否已经失效,如果没有失效,就访问它所指涉的对象

这个想法想起来容易,做起来难。由于std::weak_ptr缺乏取地址接口。写不出这样的代码,即便能写出来,也会导致竞态风险。例如如下危险代码:

if (!wpw.expired())
    auto p = *wpw;      //没有取地址接口,并且这里会存在竞态风险
                        //判断的时候没失效,但是执行的时候可能失效了

所以上述场景需要一个原子操作来检验是否悬空和使用。std::weak_ptr接口定义了这样的用法:

std::shared_ptr<Widget> spw1 = wpw.lock();  //若wpw悬空,则spw1为空
auto spw2 = wpw.lock();                     //若wpw悬空,则spw2为空
//或者用下面的方式,与上面等效
std::shared_ptr<Widget> spw3(wpw);

std::weak_ptr的使用场景1

考虑一个工厂函数,该函数基于唯一ID来创建一些指涉到只读对象的智能指针:

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

如果loadWidget成本高昂,并且ID会被频繁使用的话,一个合理的优化是,撰写一个能够完成loadWidget的工作,但又能缓存结果的函数。而缓存所有用过的Widget可能会引起性能问题,因此另一种合理的优化是,在缓存的Widget不再有人用的时候,及时将其删除

那么对于这个场景看来,返回unique_ptr就不太合适了,因为调用者会使用,但是缓存管理器也需要指涉这个对象。因此应该缓存std::weak_ptr。这里提供一个快速而粗糙的实现版本:

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的型别是std::shared_ptr
                                        //指涉到缓存的对象,如果不存在,则返回空指针

    if (!objPtr) {                      //如果对象不再缓存中,
        objPtr = loadWidget(id);        //则加载
        cache[id] = objPtr;             //并缓存
    }
    return objPtr;
}

之所以说粗糙,是因为缓存里不用的指针会越来越多。这里如何改进的方式暂时不在本讲讨论范围内。

std::weak_ptr的使用场景2

考虑这样一个使用场景,A,B,C三个对象的数据结构,A和C共享B的所有权,因此各持有一个指向B的std::shared_ptr

std::shared_ptr
std::shared_ptr
A
B
C

为了使用方便,假设有一个指针从B指向A,那么应该如何表示呢?

std::shared_ptr
std::shared_ptr
???
A
B
C

有三个选择:

  1. 裸指针: 在此情况下,A被析构,而C仍然指涉到B,B将保存着指涉到A的悬空指针。B却检查不出来,可能会产生未定义行为。

  2. std::shared_ptr:这种设计中,AB相互保存着指向对方的std::shared_ptr。这种环路实际上已经无法释放,已经内存泄露了。

  3. std::weak_ptr:避免了上述两个问题,可以判空,不会产生循环引用。

但是这里需要指出:虽然std::weak_ptr在这里可以使用,但是用std::weak_ptr打破循环引用不是特别常见的做法。类似树结构的艳歌继承谱系中,子节点通常只被其父节点拥有,当父节点被析构后,子节点也应被析构。一般来说,这种严格的接口,可以用std::unique_ptr实现父节点指向子节点,而子节点的反指向可以用裸指针安全实现,因为子节点的生命周期必定小于父节点。但不是严格的树结构就不能这么用了。

std::weak_ptr效率分析

从效率上说,std::weak_ptrstd::shared_ptr是一致的。两个指向的对象是同一个,并且拥有相同的控制块,其构造,析构,赋值操作都包含了对引用计数的原子操作。

这么说可能令你惊讶,但的确如此。std::weak_ptr不干涉共享对象所有权,因此不会影响所指涉对象的引用计数。但实际上控制块里还有第二个引用计数(弱引用计数)。更多细节请参看Item 21

要点速记
1. 使用std::weak_ptr来代替可能悬空的std::shared_ptr
2. std::weak_ptr可能的用武之地包括:缓存观察者列表,以及避免std::shared_ptr指针环路。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Effective Modern C++ Item 20 对于类似std::shared_ptr但有可能悬空的指针,使用std::weak_ptr 的相关文章

  • 如何获取正在访问 ASP.NET 应用程序的当前用户?

    为了获取系统中当前登录的用户 我使用以下代码 string opl System Security Principal WindowsIdentity GetCurrent Name ToString 我正在开发一个 ASP NET 应用程
  • EF Core Group By 翻译支持条件总和

    听说 EF Core 2 1 将支持翻译小组 我感到非常兴奋 我下载了预览版并开始测试它 但发现我在很多地方仍然没有得到翻译分组 在下面的代码片段中 对 TotalFlagCases 的查询将阻止翻译分组工作 无论如何 我可以重写这个以便我
  • C 编程 - 文件 - fwrite

    我有一个关于编程和文件的问题 while current NULL if current gt Id Doctor 0 current current gt next id doc current gt Id Doctor if curre
  • 动态加载程序集的应用程序配置

    我正在尝试将模块动态加载到我的应用程序中 但我想为每个模块指定单独的 app config 文件 假设我的主应用程序有以下 app config 设置
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • 在哪里可以找到列出 SSE 内在函数操作的官方参考资料?

    是否有官方参考列出了 GCC 的 SSE 内部函数的操作 即 头文件中的函数 除了 Intel 的 vol 2 PDF 手册外 还有一个在线内在指南 https www intel com content www us en docs in
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 如何使用 ICU 解析汉字数字字符?

    我正在编写一个使用 ICU 来解析由汉字数字字符组成的 Unicode 字符串的函数 并希望返回该字符串的整数值 五 gt 5 三十一 gt 31 五千九百七十二 gt 5972 我将区域设置设置为 Locale getJapan 并使用
  • 堆栈溢出:堆栈空间中重复的临时分配?

    struct MemBlock char mem 1024 MemBlock operator const MemBlock b const return MemBlock global void foo int step 0 if ste
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • 转发声明和包含

    在使用库时 无论是我自己的还是外部的 都有很多带有前向声明的类 根据情况 相同的类也包含在内 当我使用某个类时 我需要知道该类使用的某些对象是前向声明的还是 include d 原因是我想知道是否应该包含两个标题还是只包含一个标题 现在我知
  • 如何序列化/反序列化自定义数据集

    我有一个 winforms 应用程序 它使用强类型的自定义数据集来保存数据进行处理 它由数据库中的数据填充 我有一个用户控件 它接受任何自定义数据集并在数据网格中显示内容 这用于测试和调试 为了使控件可重用 我将自定义数据集视为普通的 Sy
  • 为什么编译时浮点计算可能不会得到与运行时计算相同的结果?

    In the speaker mentioned Compile time floating point calculations might not have the same results as runtime calculation
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 测试用例执行完成后,无论是否通过,如何将测试用例结果保存在变量中?

    我正在使用 NUNIT 在 Visual Studio 中使用 Selenium WebDriver 测试用例的代码是 我想在执行测试用例后立即在变量中记录测试用例通过或失败的情况 我怎样才能实现这一点 NUnit 假设您使用 NUnit
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK

随机推荐