条款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循环。
相关推荐
jf加菲猫5 小时前
条款21:优先选用std::make_unique、std::make_shared,而非直接new
开发语言·c++
scx201310045 小时前
20251019状压DP总结
c++
消失的旧时光-19436 小时前
Kotlin 高阶函数在回调设计中的最佳实践
android·开发语言·kotlin
LucianaiB6 小时前
掌握 Rust:从内存安全到高性能服务的完整技术图谱
开发语言·安全·rust
m0_748240256 小时前
C++ 游戏开发示例:简单的贪吃蛇游戏
开发语言·c++·游戏
Summer_Uncle6 小时前
【C++学习】指针
c++·学习
兰亭妙微7 小时前
2026年UX/UI五大趋势:AI、AR与包容性设计将重新定义用户体验
开发语言·ui·1024程序员节·界面设计·设计趋势
蜗牛沐雨7 小时前
详解C++中的字符串流
c++·1024程序员节
蜗牛沐雨7 小时前
详解C++中的流
c++·1024程序员节