掌握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

相关推荐
光影少年9 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_10 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891112 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾14 分钟前
前端基础-html-注册界面
前端·算法·html
Dragon Wu16 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym21 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫22 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫26 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat27 分钟前
前端性能优化2
前端
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js