掌握C++ 智能指针的自我引用:深入解析 shared_from_this 和 weak_from_this

shared_from_this 是 C++11 中引入的功能,允许对象在继承了 std::enable_shared_from_this 的情况下,安全地生成自身的 std::shared_ptr 实例,而不会创建新的控制块(reference counting block)。这样可以避免悬垂指针的问题,特别是在对象的成员函数中使用时,可以确保对象在使用期间不被销毁。

下面是一个简单的例子:

c++ 复制代码
#include <iostream>
#include <memory>

class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    void show() {
        std::cout << "MyClass instance" << std::endl;
    }

    std::shared_ptr<MyClass> getShared() {
        return shared_from_this();
    }
};

int main() {
    std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
    ptr->show();
    std::shared_ptr<MyClass> anotherPtr = ptr->getShared();
    // 'ptr' and 'anotherPtr' now share ownership of the same object
}

**如何正确继承和使用 **std::enable_shared_from_this

在 C++11 及其之后的版本中,为了在类的内部安全地使用 shared_from_this() 方法,类必须继承自 std::enable_shared_from_this<T>。这是因为 shared_from_this()std::enable_shared_from_this<T> 的成员函数,只有继承了这个基类的对象才具备调用它的能力。

这种设计的原因是std::enable_shared_from_this<T> 内部维护了一个 std::weak_ptr<T>。当第一个 std::shared_ptr<T> 开始管理该对象时,这个 weak_ptr 被初始化。之后,当 shared_from_this() 被调用时,它将基于这个已经存在的 weak_ptr 返回一个新的 std::shared_ptr<T>,这个新的 shared_ptr 与原有的 shared_ptr 共享对对象的所有权。

这样就避免了在对象内部直接创建新的 std::shared_ptr 实例,这种直接创建可能导致独立的所有权块的形成,增加了资源释放错误的风险。

使用注意事项

  1. 构造函数中禁用 :在对象的构造函数中使用 shared_from_this 是错误的,因为此时还没有 std::shared_ptr 实例管理该对象。
  2. 安全调用条件 :只有当至少有一个 std::shared_ptr 实例正在管理该对象时,调用 shared_from_this 才是安全的。在任何 std::shared_ptr 管理该对象之前,shared_from_this 将无法正确工作并可能抛出异常。

继承与构造行为

继承 std::enable_shared_from_this 并不改变如何构造对象。你仍需提供适当的构造函数 ,特别是当默认构造函数不适用的情况下。此外,虽然 std::enable_shared_from_this 是一个基类,但继承它并不意味着基类和派生类之间共享对象所有权。相反,这种继承关系允许派生类在必要时通过 shared_from_this() 安全地生成一个新的 std::shared_ptr 实例,这个新实例将与已经存在的管理同一对象的 shared_ptr 共享所有权。

C++ 17 之前获取weak_ptr 的做法

在 C++17 之前,当你从一个类内部需要引用自己时,你可以使用 enable_shared_from_this 来安全地获取一个 shared_ptr,但没有直接的方式来获取一个 weak_ptr

在 C++17 之前的用法中,如果你需要在类的内部获取一个指向自己的 weak_ptr,你必须先调用 shared_from_this() 来获得一个 shared_ptr,然后从这个 shared_ptr 创建一个 weak_ptr。这样的操作是安全的,但它稍显间接和不便。

例如,在 C++17 之前,你可能会这样写:

c++ 复制代码
class Listener : public std::enable_shared_from_this<Listener> {
public:
    std::weak_ptr<Listener> getWeakPtr() {
        return shared_from_this();
    }
    // ...
};

在这个例子中,getWeakPtr 方法首先调用 shared_from_this() 来获取一个 shared_ptr,然后自动将其转换为 weak_ptr

而在 C++17 中,enable_shared_from_this 类模板被增强,包括了一个 weak_from_this 方法,直接返回一个 weak_ptr,这使得代码更直接和简洁。这个改进减少了需要创建临时 shared_ptr 的场景,使得代码更加高效和易于理解。

C++17中的 **weak_from_this**功能介绍

该函数自C++17起提供了两个版本:

c++ 复制代码
std::weak_ptr<T> weak_from_this() noexcept;             // (1)
std::weak_ptr<T const> weak_from_this() const noexcept; // (2)

这两个函数返回一个std::weak_ptr<T>,该智能指针追踪所有指向*thisstd::shared_ptr实例。

返回值

函数返回一个与*this相关联的std::weak_ptr<T>,共享所有权但不增加引用计数。

**weak_from_this**的重要性

在C++17更新之前,由于std::enable_shared_from_this缺少直接获取std::weak_ptr的方法。weak_from_this填补了这一空缺,并具有以下优势:

  1. 避免循环引用 :通过weak_ptr,可以解决因对象间相互持有shared_ptr而导致的循环引用和潜在内存泄漏问题。
  2. 增强安全性weak_from_this允许安全地检查一个对象是否仍被shared_ptr管理,通过尝试从weak_ptr升级到shared_ptr来访问对象之前,确认其未被销毁。
  3. 提供灵活性 :在复杂的设计模式或对象关系中,weak_ptr提供了根据对象生命周期动态管理资源的灵活性,同时不扰乱对象的生命周期管理。

通过引入weak_from_this,C++17使得std::enable_shared_from_this在处理复杂对象关系和资源管理时变得更为灵活和安全。

weak_from_this 使用示例

下面是一个使用 std::enable_shared_from_thisweak_from_this 的 C++ 示例。这个例子模拟了一个简单的事件监听器系统,其中监听器可以注册到事件发生器上。使用 std::weak_ptr 可以防止循环引用,同时确保在尝试通知监听器时,监听器仍然存在。

c++ 复制代码
#include <iostream>
#include <vector>
#include <memory>

/**
 * @class Listener
 * @brief A listener class that can listen to events.
 * 
 * This class is derived from std::enable_shared_from_this to allow
 * listeners to provide a shared_ptr to themselves when registering
 * for events, without creating a new shared_ptr manually.
 */
class Listener : public std::enable_shared_from_this<Listener> {
public:

    void onEvent() {
        std::cout << "Event received!" << std::endl;
    }
};


class EventGenerator {
public:
    /**
     * @brief Register a listener for events.
     * 
     * @param listener A shared_ptr to the Listener to register.
     */
    void registerListener(std::shared_ptr<Listener> listener) {
        listeners.push_back(listener->weak_from_this());
    }

    /**
     * @brief Notify all registered listeners of an event.
     */
    void notifyListeners() {
        // 遍历所有注册的监听器
        for (auto weakListener : listeners) {
            // 尝试从 weak_ptr 获取 shared_ptr
            auto listener = weakListener.lock();
            // 检查返回的 shared_ptr 是否为空,确保监听器仍存在
            if (listener) {
                listener->onEvent();  // 安全调用监听器的事件处理函数
            } else {
                // 可以在这里处理监听器已销毁的情况,例如从列表中移除
                std::cout << "Listener has been destroyed and removed." << std::endl;
                // 移除逻辑代码(此处略)
            }
        }
    }

private:
    std::vector<std::weak_ptr<Listener>> listeners; ///< List of weak pointers to registered listeners.
};

int main() {
    auto eventGenerator = std::make_shared<EventGenerator>();
    auto listener1 = std::make_shared<Listener>();
    auto listener2 = std::make_shared<Listener>();

    eventGenerator->registerListener(listener1);
    eventGenerator->registerListener(listener2);

    // Simulate an event


    eventGenerator->notifyListeners();

    // Output will be:
    // Event received!
    // Event received!
    
    return 0;
}

在这个示例中,EventGenerator 类有一个方法 registerListener,它接受一个指向 Listenerstd::shared_ptr 并将其存储为 std::weak_ptr。这样做的好处是,EventGenerator 不会增加 Listener 实例的引用计数,从而防止循环引用的问题。当 EventGenerator 需要通知监听器时,它会尝试通过调用 std::weak_ptr::lock 来获取一个 std::shared_ptr,如果相关 Listener 已经被销毁,则 lock 会失败,这样就避免了访问悬空指针的风险。

这个示例展示了 std::enable_shared_from_thisweak_from_this 在复杂的对象关系和生命周期管理中的应用,特别是在事件监听系统这类场景下的有效性。

常用的使用情况:你需要引用一个对象,但又不想拥有它(即不想增加引用计数)

在复杂的系统设计和某些特定的编程模式中。这种需求通常出现在以下几种情况:

  1. 避免循环引用 :在使用智能指针管理对象生命周期的系统中,循环引用是一个常见问题。如果两个对象互相持有对方的 std::shared_ptr,它们的引用计数永远不会降到零,导致内存泄漏。在这种情况下,一个对象可以持有对另一个对象的 std::weak_ptr,以保持对该对象的访问能力,但不形成强引用循环。
  2. 观察者模式 :在观察者模式中,被观察的对象(如事件发生器)通常不需要"拥有"其观察者。观察者可能由其他部分的系统管理。在这种情况下,使用 std::weak_ptr 来引用观察者是合适的,因为它允许观察者独立于被观察对象被创建和销毁。
  3. 临时引用:在某些情况下,你可能需要临时引用一个对象,但不想因此影响其生命周期。例如,在一个复杂的图或网络结构中,节点可能需要知道与之相连的其他节点,但不需要对它们拥有所有权。
  4. 资源的安全管理 :使用 std::weak_ptr 可以避免悬挂指针的问题。当从 std::weak_ptr 转换为 std::shared_ptr 时,如果原始对象已经不存在,你会得到一个空的 shared_ptr,从而安全地表明对象不再可用。

这些情况下,std::weak_ptr 提供了一种既可以访问对象又不会延长其生命周期的方式,这对于管理复杂的对象关系和避免内存泄漏非常有用。

总结

shared_from_thisweak_from_this 的使用场景和目的有所不同。

  1. shared_from_this
    • 用途shared_from_this 用于在类的成员函数内部安全地获取一个指向当前对象的 std::shared_ptr。这适用于您需要确保当前对象在函数执行期间保持存活的场景。
    • C++11 及以后 :这个方法自 C++11 引入,适用于所有继承自 std::enable_shared_from_this 的类。
    • 场景 :例如,当一个类的成员函数需要将 this 对象作为 shared_ptr 传递给其他函数或存储它时,使用 shared_from_this
  2. weak_from_this
    • 用途 :C++17 新增的 weak_from_this 方法返回一个 std::weak_ptr,它被用于创建一个不增加引用计数的指针,这对于避免循环引用特别有用。
    • C++17 新增 :这是 C++17 新增的功能,用于获取一个 weak_ptr,从而可以在不创建额外 shared_ptr(和不增加引用计数)的情况下观察对象。
    • 场景 :当你需要引用一个对象,但又不想拥有它(即不想增加引用计数),以避免循环引用或其他所有权问题时,使用 weak_from_this

综上所述,shared_from_thisweak_from_this 都是在特定场景下的解决方案。在 C++17 之后,你拥有了更多的选择:如果需要共享所有权并确保对象在使用期间保持存活,使用 shared_from_this;如果需要引用对象但不取得所有权,以避免循环引用或其他问题,使用 weak_from_this

相关推荐
mldong4 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排4 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60615 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角6 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!6 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰6 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi8 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen8 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马9 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒9 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端