从shared_from_this引发的一个错误看weak_ptr的应用

本文探讨了在C++中使用shared_ptr时遇到的循环引用问题,通过具体案例分析了如何利用weak_ptr解决这一难题,避免std::bad_weak_ptr异常,确保资源管理的有效性和线程安全性。

从shared_from_this引发的一个错误看weak_ptr的应用


关于循环引用的概念一直了解,但理解并不深入。一直在使用shared_ptr,直到在项目中编写一个资源管理类时,指针在层级间流转造成运行时抛出std::bad_weak_ptr

在BS的《C++程序设计语言》中指出,应该尽量优先考虑使用std::unique_ptr而非std::shared_ptr。关于这一点,限于经验,在实际使用中总是很迷惑资源是否不会被共享。这个问题对我来说只能勤加思考,多多实践了。

关于循环引用的概念不多介绍,自行搜索即可。

问题

class CachePool;

class CConn final
{
public:
    CConn(const std::shared_ptr<CPool>& pCPool) 
    	: m_pCPool{pCPool}
    {  }
    ~CConn() { }
private:
    const std::shared_ptr<CPool> m_pCPool;
};




class CPool final
    : std::enable_shared_from_this<CPool>
{
    const int MAX_CACHE_CONN_CNT = 2;
public:
    CPool();
    ~CPool();

    int Init()
    {
    	for (int i = 0; i < m_cur_conn_cnt; ++i){
        	auto pConn = std::make_shared<CConn>(shared_from_this());
        	m_free_list.push_back(pConn);
    	}
    	return 0;
	}

private:
    int m_cur_conn_cnt = MAX_CACHE_CONN_CNT;
    std::list<std::shared_ptr<CConn>> m_free_list;
};

该代码在运行时抛出std::bad_weak_ptr异常。

在这里插入图片描述

可以看到该代码,在管理类CPool中拥有一个队列m_free_list中存储着资源类CConn的智能指针列表,而在资源类中拥有一个指向管理类的std::shared_ptr,用以访问管理类的接口,以提供对各个资源类都适用的信息。

以此形成循环引用。

解决方案

舍弃资源类中指向管理类的指针

可以将管理类中被资源类所需要的信息,在资源类的构造过程中传递给资源类,在资源类中以const &的形式进行存储与访问。

class CachePool;

class CConn final
{
public:
    CacheConn(const int& cur_conn_cnt;) 
    	: pool_cur_conn_cnt{cur_conn_cnt}
    {  }
    ~CacheConn() { }
private:
    const int& pool_cur_conn_cnt;
};




class CPool final
    : std::enable_shared_from_this<CachePool>
{
    const int MAX_CACHE_CONN_CNT = 2;
public:
    CachePool();
    ~CachePool();

    int Init()
    {
    	for (int i = 0; i < m_cur_conn_cnt; ++i){
        	auto pConn = std::make_shared<CConn>(m_cur_conn_cnt);
        	m_free_list.push_back(pConn);
    	}
    	return 0;
	}

private:
    int m_cur_conn_cnt = MAX_C_CONN_CNT;
    std::list<std::shared_ptr<CConn>> m_free_list;
};

这是一种限制性很大的解法。

其一,如果管理类中拥有的共享资源过多,导致资源类的参数列表过于繁冗。违反了接口简洁的设计原则。

其二,引用作为与指针类似概念的间接访问方式,拓展了资源的访问渠道,失去了std::shared_ptr提供的线程安全保障:

在这里插入图片描述

也就是说,在多线程情况下,裸资源的线程安全性转移在程序员手中来维护,需要在每个资源类的使用中进行同步,增大了很多工作量,以及复杂的同步性问题中的出错概率和效率损失。(很难保证加锁粒度对性能的影响是否比share_ptr更低)。

使用weak_ptr打破循环引用

class CachePool;

class CConn final
{
public:
    CConn(const std::weak_ptr<CPool>& pCPool) 
    	: m_pCPool{pCPool}
    {  }
    ~CConn() { }
private:
    const std::weak_ptr<CPool> m_pCPool;
};




class CPool final
    : std::enable_shared_from_this<CPool>
{
    const int MAX_CACHE_CONN_CNT = 2;
public:
    CachePool();
    ~CchePool();

    int Init()
    {
    	for (int i = 0; i < m_cur_conn_cnt; ++i){
        	auto pConn = std::make_shared<CConn>(weak_from_this());
        	m_free_list.push_back(pConn);
    	}
    	return 0;
	}

private:
    int m_cur_conn_cnt = MAX_CACHE_CONN_CNT;
    std::list<std::shared_ptr<CConn>> m_free_list;
};

编译通过。

<think> 在C++中,`shared_from_this`是一个用于在类的成员函数内部获取指向当前对象的`shared_ptr`的工具。它通常用于当某个对象已经被`shared_ptr`管理,并且需要在该对象的成员函数中传递指向自身的`shared_ptr`时。使用`shared_from_this`可以避免创建额外的`shared_ptr`(这会导致引用计数混乱,从而可能导致对象被多次删除)。 ### 使用场景 假设我们有一个类`A`,它需要在一个成员函数中传递自身的`shared_ptr`给其他函数(例如,用于回调、异步操作等)。如果我们直接创建一个新的`shared_ptr`指向`this`,那么就会有两个独立的`shared_ptr`指向同一个对象,这会导致对象被销毁两次(因为每个`shared_ptr`都会在引用计数为0时尝试删除对象)。使用`shared_from_this`可以解决这个问题,因为它返回一个与已有`shared_ptr`共享所有权的`shared_ptr`。 ### 使用方法 1. 类需要继承自`std::enable_shared_from_this<T>`,其中`T`是类自身的类型。 2. 在需要获取`shared_ptr`指向当前对象的地方,调用`shared_from_this()`成员函数。 3. 注意:在调用`shared_from_this()`之前,必须已经有一个`shared_ptr`管理当前对象(即对象必须是通过`shared_ptr`构造的),否则会抛出`std::bad_weak_ptr`异常。 ### 代码示例 ```cpp #include <memory> #include <iostream> class MyClass : public std::enable_shared_from_this<MyClass> { public: void do_something() { // 传递自身的shared_ptr给其他函数 process(shared_from_this()); } void process(std::shared_ptr<MyClass> ptr) { std::cout << "Use count: " << ptr.use_count() << std::endl; } }; int main() { // 创建MyClass对象,并用shared_ptr管理 std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(); ptr->do_something(); // 输出:Use count: 2(因为函数内部还有一个shared_ptr) return 0; } ``` ### 注意事项 1. **构造要求**:必须保证对象已经被`shared_ptr`所管理。也就是说,不能通过栈上对象(局部变量)或者全局对象来调用`shared_from_this`,因为这样没有外部的`shared_ptr`来共享所有权。例如,以下代码是错误的: ```cpp MyClass obj; // 错误:栈上对象 obj.do_something(); // 会抛出异常,因为没有外部shared_ptr管理它 ``` 2. **不能在构造函数中调用**:在构造函数中,对象尚未被`shared_ptr`接管(因为构造`shared_ptr`是在对象构造完成后进行的),因此在构造函数中调用`shared_from_this()`同样会抛出异常。 3. **继承要求**:类必须公有继承`std::enable_shared_from_this`,并且使用CRTP(Curiously Recurring Template Pattern)模式,即指定自身为模板参数。 4. **线程安全**:`shared_from_this()`是线程安全的,前提是对象的内部状态(引用计数)是线程安全的(标准库的`shared_ptr`引用计数是原子的)。 ### 为什么需要`enable_shared_from_this`? 在类的内部,我们无法直接知道是否已经有一个`shared_ptr`管理了当前对象。`std::enable_shared_from_this`在内部存储了一个弱指针(`weak_ptr`),当对象被`shared_ptr`管理时,这个弱指针会被初始化。调用`shared_from_this()`时,会通过这个弱指针创建一个`shared_ptr`,从而共享所有权。 ### 底层原理 当我们通过`shared_ptr`构造一个继承自`enable_shared_from_this`的对象时,`shared_ptr`的构造函数会检测该类是否继承自`enable_shared_from_this`。如果是,则会将`shared_ptr`赋值给`enable_shared_from_this`内部保存的`weak_ptr`。这样,当调用`shared_from_this()`时,就可以通过这个`weak_ptr`的`lock()`方法获得一个`shared_ptr`。 ### 错误示例 ```cpp class MyClass : public std::enable_shared_from_this<MyClass> { public: void start() { // 错误:试图在构造函数中调用shared_from_this // 因为此时对象还没有被shared_ptr管理 // auto self = shared_from_this(); // 会抛出异常 } }; int main() { MyClass* raw_ptr = new MyClass; raw_ptr->start(); // 错误:即使对象在堆上,但没有被shared_ptr管理 delete raw_ptr; // 正确做法 std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(); ptr->start(); // 安全 return 0; } ``` ### 总结 `shared_from_this`提供了一种安全获取当前对象`shared_ptr`的方式,避免了多个独立`shared_ptr`管理同一对象。使用时需要确保对象已经被`shared_ptr`管理,并且不能在构造函数中使用。 §§相关问题§§: 1. 为什么不能在构造函数中调用`shared_from_this`? 2. 使用`shared_from_this`时,如果对象不是由`shared_ptr`管理会发生什么? 3. `std::enable_shared_from_this`的内部实现原理是什么? 4. 在多线程环境中使用`shared_from_this`是否安全? </think> ### `shared_from_this`详解 `std::enable_shared_from_this` 是 C++11 引入的模板类,用于解决对象自身需要获取指向自己的 `std::shared_ptr` 的问题。主要应用场景是在类的成员函数中需要传递指向当前对象的智能指针时。 #### 为什么需要 `shared_from_this`? 1. **避免重复创建所有权**: 直接在成员函数中创建新的 `shared_ptr(this)` 会导致多个独立的引用计数,造成双重删除错误 2. **安全传递自身所有权**: 在异步操作、回调函数或事件处理中,需要安全地延长对象生命周期 3. **打破循环引用**: 在循环依赖场景中配合 `weak_from_this()` 使用 #### 核心机制 1. 类必须**公有继承** `std::enable_shared_from_this<T>` 2. 内部维护一个 `weak_ptr` 观察者指针 3. 当通过 `shared_ptr` 构造对象时,自动初始化内部 `weak_ptr` 4. `shared_from_this()` 通过内部 `weak_ptr` 安全创建 `shared_ptr` #### 正确使用示例 ```cpp #include <memory> #include <iostream> class Session : public std::enable_shared_from_this<Session> { public: void start() { // 错误方式:会创建新控制块 // std::shared_ptr<Session> bad_ptr(this); // 正确方式:共享所有权 auto self = shared_from_this(); std::cout << "Use count: " << self.use_count() << "\n"; } void async_operation() { // 模拟异步操作传递自身指针 auto self = shared_from_this(); std::thread([self]() { self->process_data(); }).detach(); } void process_data() { std::cout << "Processing data in background thread\n"; } }; int main() { // 必须通过shared_ptr创建对象 auto session = std::make_shared<Session>(); session->start(); // Use count: 2 session->async_operation(); // 等待后台线程完成 std::this_thread::sleep_for(std::chrono::milliseconds(100)); } ``` #### 关键注意事项 1. **构造约束**: - 对象**必须**通过 `shared_ptr` 管理 - 禁止在栈上或裸指针直接创建对象 ```cpp // 错误示例:栈上对象 Session local_session; local_session.start(); // 抛出std::bad_weak_ptr异常 ``` 2. **构造函数限制**: - **禁止**在构造函数中调用 `shared_from_this()` - 因为此时内部 `weak_ptr` 尚未初始化 ```cpp class BadExample : public std::enable_shared_from_this<BadExample> { public: BadExample() { // 错误!抛出std::bad_weak_ptr auto p = shared_from_this(); } }; ``` 3. **多线程安全**: - `shared_from_this()` 是线程安全的 - 但需要同步访问对象状态 #### 高级技巧:`weak_from_this()` C++17 引入的 `weak_from_this()` 用于打破循环引用: ```cpp class Node : public std::enable_shared_from_this<Node> { std::vector<std::weak_ptr<Node>> children; public: void add_child(std::shared_ptr<Node> child) { children.push_back(child->weak_from_this()); } }; ``` #### 典型应用场景 1. 网络编程(连接会话管理) 2. 异步任务处理 3. 观察者模式 4. 回调函数注册 5. 需要延长对象生命周期的场景
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值