weak_ptr

前提

​ 好的你现在已经知道了unique_ptr和shared_ptr两种指针,使用确保指针的独享,shared_ptr确保指针的共享,那么weak_ptr是干嘛的?

作用

​ 既然shared_ptr是内部包含引用计数的强引用,那么有这样需求:

  1. 不对资源进行 管理,但是可以使用这个堆对象机能
  2. 可以知道这个堆对象是否被析构

正确的使用方式

​ 其实可以把weak_ptr看作shared_ptr的扩充,weak_ptr无法进行解引用

  1. weak_ptr是从shared_ptr创建而来,但是不改变shared_ptr内部的引用计数

  2. 怎么使用weak_ptr呢?weak_ptr自带一个expired()判断引用计数是否为0

      shared_ptr<int> test(make_shared<int>());
      weak_ptr<int> weak(test);
      test.reset();
      std::cout << weak.expired() << std::endl;
    

    那么你也可能会想使用解引用的方式得到内部指针,首先weak_ptr不允许这种行为。就算允许容易引起条件竞争:

    在 weak.expired() 和 解引用 中间,另一个线程将这片内存释放,这样就出现未定义行为了

  3. 你需要的是 原子性判断堆对象是否存在,存在则返回shared_ptr,不存在返回空shared_ptr

    lock出现了,内部并不是很复杂的操作

     shared_ptr<_Tp>
    lock() const noexcept {
    		return this->expired() ? shared_ptr<_Tp>() : shared_ptr<_Tp>(*this);
    }
    
  4. 正确的使用方式

      shared_ptr<int> test(make_shared<int>());
      weak_ptr<int> weak(test);
      shared_ptr<int> test_weak = weak.lock();
      if (test_weak) {
          std::cout << "use lock get shared" << std::endl;
      }
    

使用场景

  1. 对象池

    保存已有对象的资源,当下一次相同的需求到来,直接返回对象池的内容

    方案是这样,使用ID值标识每个资源,当ID不存在则创建资源,当ID存在直接返回资源。当没人指向这个资源将ID销毁

    #include <iostream>     // std::streambuf, std::cout
    #include <memory>      // std::ofstream
    #include <functional>      // std::ofstream
    #include <unordered_map>      // std::ofstream
    
    using std::shared_ptr;
    using std::weak_ptr;
    using std::unordered_map;
    using std::unique_ptr;
    
    template <typename T, typename ...Args>
    std::unique_ptr<T> make_unique(Args&& ...args) {
        return unique_ptr<T>(new T(std::forward<Args>(args)...));
    }
    
    shared_ptr<const int> LoadFastFactory(int id) {
        static unordered_map<int, weak_ptr<const int>> cache;
        auto cache_iter = cache.find(id);
        shared_ptr<const int> cache_ptr;
    
        if (cache_iter != cache.end()) {
            cache_ptr = (cache_iter->second).lock();
        }
    
        if (!cache_ptr) {
            cache_ptr = make_unique<const int>(id);
            cache[id] = cache_ptr;
        }
        return cache_ptr;
    }
    
    int main () {
        shared_ptr<const int> test_1 = LoadFastFactory(1);
        shared_ptr<const int> test_2 = LoadFastFactory(1);
        std::cout << (test_1 == test_2) << std::endl;
        return 0;
    }
    

    使用 make_unique进行资源的创建

    cache进行资源的快速缓存获取

    shared_ptr用于在没人使用资源的时候进行资源的自动析构,上述代码有问题,在于即使资源被删除,ID仍旧存在

    1. Observer design pattern

    观察者模式的核心就是 observable将需要通知的事情通知每个Observer,在这期间还需要确保Observer不是destory的

    实现起来就是,observable用weak_ptr存放Observer,这样也带来限制 Observer也是用shared_ptr管理的

  2. 循环引用

    A内部持有shared_ptr的B

    C内部持有shared_ptr的B

    B需要使用A的功能,那么请问A应该是什么样的形式存在呢?

    1. 原始指针?,A可能存在销毁的可能性,带来悬挂引用的危险
    2. shared_ptr ? A 持有 B,B 持有 A,导致互相的shared_ptr计数都为1,无法释放堆内存
    3. weak_ptr ?可行,因为waek_ptr不参与共享指针的引用计数

    实际上,在工程中对于这种例子,本质上是类之间结构的问题

    首先需要明确一点:使用shared_ptr就是在管理堆对象的生命周期,使用weak_ptr不负责管理堆对象的使用周期

    工程上一般对这种方式处理循环引用是很少的,为啥呢,因为所有出现循环引用的问题都是因为类设计问题,也就是说保证 类之间的耦合关系,就像一个Tree结构,父节点使用shared_ptr包含子节点,子节点使用weak_ptr包含父节点一样

额外

​ weak_ptr内部保存和shared_ptr相同的block ptr

posted @ 2021-06-14 21:26  make_wheels  阅读(194)  评论(0编辑  收藏  举报