条款20:对于类似std::shared_ptr但有可能空悬的指针使用std::weak_ptr

1 std::weak_ptr

  1. std::weak_ptr是对 std::shared_ptr的增强,不能被解引用,不能测试是否为空,不会影响所指向对象的引用计数:
cpp 复制代码
auto spw = std::make_shared<Widget>();  // 引用计数=1
...
std::weak_ptr<Widget> wpw(spw);         // 引用计数=1
...
spw = nullptr;                          // 引用计数=0,spw变为空悬
if (wpw.expired()) ...                    // 如果 wpw 没有指向任何对象
  1. 但通常你希望检查 std::weak_ptr是否有效,如果它不是悬挂的,则访问它指向的对象。
    1)因为 std::weak_ptr缺乏解引用操作,所以无法编写这样的代码。
    2)需要原子操作,检查 std::weak_ptr是否有效, 有效则从 std::weak_ptr创建一个 std::shared_ptr来进行访问。
cpp 复制代码
std::shared_ptr<Widget> spw1 = wpw.lock(); 	// 如果 wpw 无效, spw1 将会是空
auto spw2 = wpw.lock(); 			// 同上
  1. std::shared_ptr可以使用std::weak_ptr作为参数进行构造,如果std::weak_ptr已经无效,就会抛出一个异常:
cpp 复制代码
std::shared_ptr<Widget> spw3(wpw); // 如果 wpw 无效, 则抛出 std::bad_weak_ptr
  1. 对于根据 ID 生成只读对象的工厂函数,其调用可能很昂贵,需要缓存,并在不再使用时销毁缓存的 Widget:
cpp 复制代码
std::unique_ptr<const Widget> loadWidget(WidgetID id);

1)调用者当然应该获得对象的智能指针,并且确定这些对象的生命周期,但缓存也需要指向这些对象的指针。

2)缓存的指针需要能够检测到它们悬空。因此,缓存的指针应该是std::weak_ptrs。

3)这意味着工厂的返回类型应该是std::shared_ptr。

cpp 复制代码
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;
}

2 std::weak_ptr使用

  1. "观察者设计模式"是std::weak_ptrs的另一个用例。
    1)该模式的主要组成部分是主题(状态可能发生变化的对象)和观察者。主题只关注观察者是否已经被销毁。
    2)每个主题持有一个元素为观察者的std::weak_ptr的容器,从而使主题能够在使用指针之前确定它是否悬空。
  2. 最后一个std::weak_ptr的用例:
    考虑一个包含对象 A、B 和 C 的数据结构,其中 A 和 C 共享 B 的所有权,因此持有对 B 的std::shared_ptr,假设从 B 到 A 也有一个指针,该如何设计?

    1)raw pointer:如果 A 被销毁,C 仍然指向 B,B 将包含一个指向 A 的悬空指针。
    2)std::shared_ptr:A 和 B 都包含对彼此的 std::shared_ptr。产生的 std::shared_ptr 循环(A 指向 B,B 指向 A)将阻止 A 和 B 被销毁。
    3)std::weak_ptr:这避免了上述两个问题。如果 A 被销毁,B 指向它的指针将悬空,但 B 能够检测到这一点。此外,尽管 A 和 B 会相互指向,但 B 的指针不会影响 A 的引用计数。
  3. std::weak_ptr 对象的大小与 std::shared_ptr 对象相同,它们都使用相同的控制块。std::weak_ptr 不参与对象的共享所有权,因此不会影响所指向对象的引用计数。但是,std::weak_ptr 确实会操纵控制块中的第二个引用计数,用于检测所指向的对象是否已经被销毁或过期。

3 要点速记

  1. 对于类似std::shared_ptr的可能会悬挂的指针,请使用std::weak_ptr。
  2. std::weak_ptr的潜在用例包括缓存、观察者列表以及防止std::shared_ptr循环。
相关推荐
BuHuaX13 分钟前
Lua 与 ToLua:运行机制及热更新原理
开发语言·unity·junit·c#·lua
xys_67815 分钟前
深挖C++:C++入门基础
开发语言·c++
高冷小鱼殿下40 分钟前
C 语言指针与输入输出
c++
幻境如梦丶皆为虚1 小时前
从零写一个 DSH 插件:在左侧菜单栏加一个「测试」
开发语言·javascript·ecmascript
LabVIEW开发1 小时前
LabVIEW 局部变量与数据流:这道 CLAD 题为什么答案是 Undefined
java·开发语言·labview
路溪非溪1 小时前
C++标准库之智能指针
linux·开发语言·c++
hetao17338372 小时前
2026-11-02 hetao1733837 的刷题记录
c++·算法
衍生星球2 小时前
【Java 语言】— 面向对象:构造方法(无参、有参构造)
java·开发语言
Tairitsu_H2 小时前
[C++高阶数据结构·图论下] 最短路径:为什么“挑最小的”一定正确?
数据结构·c++·图论