程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

C++11:weak_ptr的设计哲学(观察而不拥有)

balukai 2025-08-01 15:47:01 文章精选 4 ℃

0.简介

循环引用是开发中的常见问题,特别是在使用引用计数(shared_ptr)时尤为突出,为了解决这种问题,C++引入weak_ptr,其通过“观察而非拥有”的思想,来解决循环引用的问题。本文将对weak_ptr进行原理和源码解读、带给我们的设计启示以及如何与shared_ptr进行协作等方面进行介绍。

1.问题引入

我们来看下面一段代码,其有两个类,分别持有另一个类的shared_ptr指针,这样出作用域后两者引用计数都不为空,导致内存泄漏,其会导致资源长期累积。

class B; // 前向声明


class A {
public:
    std::shared_ptr<B> b_ptr;
};


class B {
public:
    std::shared_ptr<A> a_ptr;
};


void createCycle() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;
    // 离开作用域后a和b的引用计数仍为1,无法释放
}

2.weak_ptr原理

weak_ptr的像模板,引用计数等原理和shared_ptr类似,我们来看其独有的一些东西,工作原理如下:

1)弱引用计数:weak_ptr不控制对象的声明周期,而是通过共享的shared_ptr控制块(包含强引用和弱引用计数)来跟踪对象是否存在,当强引用计数降为0时,对象被销毁,但控制块依然会保留,直到弱引用计数也变为0。

2)可观测性:通过共享控制块来检测对象是否已经被释放。

3)安全访问:通过lock()方法临时获取可用的shared_ptr。

从工作原理我们可以得到一些设计上的启发:

1)间接访问:不论是shared_ptr还是weak_ptr都是在原指针基础上增加了一层,通过控制块来间接访问从而达到各种控制的目的。

2)权限控制:通过对控制权限的限制(比如强计数可以控制生命周期,而若计数只影响控制块的生命周期)来实现不同的能力。

3)显示提升权限:通过lock()来明确获取shared_ptr,从而防止一些使用时的隐藏问题。

3.weak_ptr源码解读

要了解weak_ptr,首先先明确其和shared_ptr的关系,两者互为友元类,接下来也先从成员变量来看,weak_ptr继承自__weak_ptr,所以我们来看__weak_ptr的成员变量,主要包括两个:

_Tp*         _M_ptr;         // Contained pointer.
__weak_count<_Lp>  _M_refcount;    // Reference counter.

首先时_M_ptr,这个比较好理解,就是托管的普通内存指针,另外一个变量时_M_refcount,其类型为__weak_count<_Lp>,这个我们可以看一下其内部的实现,其真正来源就是shared_ptr中的_M_pi,所以可以共享引用计数:

  template<_Lock_policy _Lp>
    class __weak_count
    {
    public:
      __weak_count()
      : _M_pi(0) // nothrow
      { }
  
      __weak_count(const __shared_count<_Lp>& __r)
      : _M_pi(__r._M_pi) // nothrow
      {
  if (_M_pi != 0)
    _M_pi->_M_weak_add_ref();
      }
      
      __weak_count(const __weak_count<_Lp>& __r)
      : _M_pi(__r._M_pi) // nothrow
      {
  if (_M_pi != 0)
    _M_pi->_M_weak_add_ref();
      }
      
      ~__weak_count() // nothrow
      {
  if (_M_pi != 0)
    _M_pi->_M_weak_release();
      }
      
      __weak_count<_Lp>&
      operator=(const __shared_count<_Lp>& __r) // nothrow
      {
  _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
  if (__tmp != 0)
    __tmp->_M_weak_add_ref();
  if (_M_pi != 0)
    _M_pi->_M_weak_release();
  _M_pi = __tmp;  
  return *this;
      }
      
      __weak_count<_Lp>&
      operator=(const __weak_count<_Lp>& __r) // nothrow
      {
  _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
  if (__tmp != 0)
    __tmp->_M_weak_add_ref();
  if (_M_pi != 0)
    _M_pi->_M_weak_release();
  _M_pi = __tmp;
  return *this;
      }
      void
      _M_swap(__weak_count<_Lp>& __r) // nothrow
      {
  _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
  __r._M_pi = _M_pi;
  _M_pi = __tmp;
      }
  
      long
      _M_get_use_count() const // nothrow
      { return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }
      friend inline bool
      operator==(const __weak_count<_Lp>& __a, const __weak_count<_Lp>& __b)
      { return __a._M_pi == __b._M_pi; }
      
      friend inline bool
      operator<(const __weak_count<_Lp>& __a, const __weak_count<_Lp>& __b)
      { return std::less<_Sp_counted_base<_Lp>*>()(__a._M_pi, __b._M_pi); }
    private:
      friend class __shared_count<_Lp>;
      _Sp_counted_base<_Lp>*  _M_pi;
    };

了解了成员变量,我们来了解一下其内部的成员函数,主要来看lock函数,其如何操作来升级权限为shared_ptr,其实现较为简单,直接构造一个shared_ptr,也就是shared_ptr支持使用这种方式构造,对其强引用计数加一即可。

shared_ptr<_Tp>
      lock() const noexcept
      { return shared_ptr<_Tp>(*this, std::nothrow); }

4.与shared_ptr协作

#include <iostream>
#include <memory>
// 案例1:循环引用导致内存泄漏
struct LeakyB;  // 前向声明
struct LeakyA {
    std::shared_ptr<LeakyB> b_ptr;
    ~LeakyA() { std::cout << "LeakyA destroyed" << std::endl; }
};
struct LeakyB {
    std::shared_ptr<LeakyA> a_ptr;  // 错误:使用shared_ptr导致循环引用
    ~LeakyB() { std::cout << "LeakyB destroyed" << std::endl; }
};
// 案例2:使用weak_ptr解决循环引用
struct SafeB;  // 前向声明
struct SafeA {
    std::shared_ptr<SafeB> b_ptr;
    ~SafeA() { std::cout << "SafeA destroyed" << std::endl; }
};
struct SafeB {
    std::weak_ptr<SafeA> a_ptr;  // 关键:使用weak_ptr打破循环
    ~SafeB() { std::cout << "SafeB destroyed" << std::endl; }
    
    // 安全访问A的方法
    void accessA() const {
        if (auto shared_a = a_ptr.lock()) {
            std::cout << "SafeB accessed SafeA successfully" << std::endl;
        } else {
            std::cout << "SafeA is already destroyed" << std::endl;
        }
    }
};
int main() {
    std::cout << "=== 案例1:循环引用导致内存泄漏 ===" << std::endl;
    {
        auto leaky_a = std::make_shared<LeakyA>();
        auto leaky_b = std::make_shared<LeakyB>();
        
        // 互相引用,形成循环
        leaky_a->b_ptr = leaky_b;
        leaky_b->a_ptr = leaky_a;
        
        std::cout << "LeakyA use count: " << leaky_a.use_count() << std::endl;  // 输出2
        std::cout << "LeakyB use count: " << leaky_b.use_count() << std::endl;  // 输出2
    }  // 离开作用域时,LeakyA和LeakyB的析构函数不会被调用(内存泄漏)
    
    std::cout << "\n=== 案例2:使用weak_ptr解决循环引用 ===" << std::endl;
    {
        auto safe_a = std::make_shared<SafeA>();
        auto safe_b = std::make_shared<SafeB>();
        
        // 互相引用,但SafeB::a_ptr是weak_ptr
        safe_a->b_ptr = safe_b;
        safe_b->a_ptr = safe_a;
        
        std::cout << "SafeA use count: " << safe_a.use_count() << std::endl;  // 输出1
        std::cout << "SafeB use count: " << safe_b.use_count() << std::endl;  // 输出1
        
        // 通过weak_ptr安全访问对象
        safe_b->accessA();  // 输出:SafeB accessed SafeA successfully
    }  // 离开作用域时,SafeA和SafeB的析构函数会被正确调用
    
    std::cout << "\n=== 验证weak_ptr的过期行为 ===" << std::endl;
    {
        std::weak_ptr<SafeA> weak_ref;
        
        {
            auto temp_a = std::make_shared<SafeA>();
            weak_ref = temp_a;
            std::cout << "Weak reference use count: " << weak_ref.use_count() << std::endl;  // 输出1
        }  // temp_a被销毁
        
        std::cout << "Weak reference use count after destruction: " 
                  << weak_ref.use_count() << std::endl;  // 输出0
        
        if (auto shared_a = weak_ref.lock()) {
            std::cout << "Object still exists" << std::endl;
        } else {
            std::cout << "Object has been destroyed" << std::endl;  // 执行此分支
        }
    }
    
    return 0;
}

其输出如下:

最近发表
标签列表