网站首页 > 文章精选 正文
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;
}
其输出如下:
猜你喜欢
- 2025-08-01 游戏里的 boss 每天都在干嘛?不寂寞吗?
- 2025-08-01 秋招C++八股--封装、继承、多态(持续更新)
- 2025-08-01 Qt QDebug格式输出、自定义类输出及重定向输出Log
- 2025-08-01 C++20 新特性(24):模板访问权限和typename的放宽
- 2025-08-01 c++学习大纲总结
- 2025-08-01 C++学习教程_C++语言随到随学_不耽误上班_0基础
- 2025-08-01 程序员效率分享:加速C ++编译
- 2025-08-01 零基础学编程不知道如何下手,该怎么办
- 2025-08-01 C++常用知识点汇总(基础)
- 2025-08-01 C++特性使用建议
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)
- mysql数据库面试题 (57)
- fmt.println (52)