# C++:深入理解 std::enable_shared_from_this 与 shared_from_this

# 为什么以及何时使用 shared_from_this

# shared_from_this 的作用

在面向对象编程中,常常需要在对象的成员函数中获取指向自身的智能指针,以确保对象在某些操作(特别是异步操作)期间的生命周期。 std::enable_shared_from_thisshared_from_this 正是为了解决这一需求而设计。

# 使用场景

  1. 异步操作与回调函数
    在涉及异步操作(如异步 I/O、定时器、线程等)时,需要确保对象在异步操作完成之前不会被销毁。常见的场景包括:
  • 网络编程:使用异步 I/O 操作读取或写入数据,需要在回调函数中访问对象的成员。
  • 定时器回调:设置定时器后,在定时器触发的回调函数中需要访问对象。
  • 线程池与任务调度:将对象的成员函数作为任务提交到线程池,需要确保对象在任务执行期间存活。
  1. 防止对象过早销毁
    当对象的生命周期可能在外部被管理,但在内部需要延长自身的生命周期。例如:
  • 事件发布 - 订阅模式:对象发布事件,订阅者可能异步处理,发布者需要确保在所有订阅者处理完之前不被销毁。
  • 资源管理类:对象管理着一些资源,需要在异步清理或回收资源时防止自身被销毁。

# 必要性和优点

  1. 确保对象生命周期的安全
    使用 shared_from_this 可以获取到自身的 std::shared_ptr ,从而增加对象的引用计数,确保对象在异步操作或回调过程中不会被销毁,避免出现悬空指针和未定义行为。

  2. 简化内存管理
    通过智能指针自动管理对象的生命周期,减少了手动管理内存的复杂性,降低了内存泄漏和双重释放的风险。

  3. 防止多重所有权问题
    直接在类内部使用 std::shared_ptr(this) 可能导致多个独立的 shared_ptr 管理同一对象,造成引用计数不一致。 std::enable_shared_from_this 确保了所有的 shared_ptr 共享同一个引用计数。下面写一段出错的代码。

    class NetCallback : public INetCallback {
    public:
        NetCallback(std::weak_ptr<NetClient> client) : client_(client) {}
        ~NetCallback() = default;
        void NetChanged(std::shared_ptr<NetChangedInfo> info) {
            if(auto client = client_.lock()) {
                client->OnNetChanged(info);
            }
        }
    private:
        std::weak_ptr<NetClient> client_;
    };
    void NetClient::OnNetChanged(std::shared_ptr<NetChangedInfo> info) {
        // do something
    }
    void NetClient::RegisterCallback(std::shared_ptr<INetCallback> callback)
    {
        auto proxy = GetProxy();
        proxy->RegisterCallback(std::make_shared<NetCallback>(this)); 
        // 这里 this 应改成 shared_from_this,否则创建的 `std::shared_ptr<NetCallback>` 只是临时对象。
        // 临时 shared_ptr 会在析构后 delete 外部的 `NetClient` 对象。
    }

# 示例

以下是一个使用 std::enable_shared_from_this 的典型示例:

#include <iostream>
#include <memory>
class AsyncWorker : public std::enable_shared_from_this<AsyncWorker> {
public:
    void StartAsyncOperation() {
        // 假设这是一个异步操作的模拟
        std::thread([sp = shared_from_this()]() { // 在异步线程中创建新的 shared_ptr,确保对象不被销毁
            sp->DoWork(); // 模拟长时操作
        }).detach();
    }
    void DoWork() {
        // 执行实际工作
        std::cout << "Async work is done." << std::endl;
    }
};
int main() {
    auto worker = std::make_shared<AsyncWorker>();
    worker->StartAsyncOperation();
    // 主线程可能在此退出,但 AsyncWorker 对象仍然存活,直到异步操作完成
    //shared_ptr 不随当前线程结束而析构
    return 0;
}

在上述代码中:
可能出现的错误写法:

  • 线程通过 std::shared_ptr 创建 shared_ptr,会导致子线程结束里面释放持有的对象,主线程中的 this 就变成了悬空指针。
    void StartAsyncOperation() {
        std::thread([sp = std::make_shared(this)]() {
            sp->DoWork();
        }).detach();
    }
  • 如果不使用 shared_from_this ,在 StartAsyncOperation 中捕获 this ,一旦 worker 在主线程中超出作用域被销毁,异步线程中的 this 就变成了悬空指针。
    void StartAsyncOperation() {
        std::thread([this]() {
            this->DoWork();
        }).detach();
    }

# shared_from_this 的进阶用法

# std::weak_ptr

在某些情况下,需要持有对象的弱引用,以避免循环引用导致的内存泄漏。std::weak_ptr 可以与 shared_from_this 结合使用。

  • 观察者模式

    • 观察者模式是在 subject 状态发生改变时,通知观察者的一种设计模式。
    • 在多数实现中,每个 subject 持有指向观察者的指针,这使得当 subject 状态改变时可以很容易通知观察者。
    • subject 不会控制其观察者的生存期,因此应该是持有观察者的 weak_ptr 指针。同时在 subject 的使用某个指针时,可以先确定是否空悬。
    class Observer {
    public:
        virtual void OnNotify() = 0;
    };
    class Subject : public std::enable_shared_from_this<Subject> {
    private:
        Subject() = default;
        ~Subject() = default;
    public:
        static std::shared_ptr<Subject> GetInstance() {
            static std::shared_ptr<Subject> instance = new Subject;
            return instance;
        }
        void AddObserver(std::weak_ptr<Observer> observer) {
            observers_.push_back(observer);
        }
        void NotifyObservers() {
            for (auto it = observers_.begin(); it != observers_.end(); ) {
                if (auto obs = it->lock()) {
                    obs->OnNotify();
                    ++it;
                } else {
                    it = observers_.erase(it); // 移除已销毁的观察者
                }
            }
        }
    private:
        std::vector<std::weak_ptr<Observer>> observers_;
    };
  • 回调使用外部对象
    回调函数想使用外部对象,又不想让回调类持有外部对象,控制外部对象的生命周期

    class TestClient : std::enable_shared_from_this<TestClient> {
    private:
        TestClient() = default;
        ~TestClient() = default;
    public:
        static std::shared_ptr<TestClient> GetInstance() {
            static std::shared_ptr<TestClient> instance = new TestClient;
            return instance;
        }
        void RegisterObserver() {
            // 将自身 shared_ptr 转成 weak_ptr,让 TestObserver 持有,TestObserver 也不会影响自身的生命周期
            Subject::GetInstance()->AddObserver(std::make_shared<TestObserver>(shared_from_this()));
        }
        void HandleChanged() {
            // DoSomething()
        }
    private:
        class TestObserver : public Observer {
        public:
            TestObserver(std::weak_ptr<> client) : client_(client) {}
            ~TestObserver() = default;
            void OnNotify() override {
                auto client = client_.lock();
                if (client == nullptr) {
                    return;
                }
                client->HandleChanged();
            }
        private:
            std::weak_ptr<TestClient> client_;
        };
    };
    int main() {
        TestClient::GetInstance()->RegisterObserver();
        Subject::GetInstance()->NotifyObservers();
        return 0;
    }

# shared_from_this 的陷阱

# 常见陷阱

  1. 对象未由 std::shared_ptr 管理
    问题描述:如果对象未由 std::shared_ptr 管理,直接调用 shared_from_this() 将导致未定义行为,通常会抛出异常。

    解决方法:

    • 确保对象由 std::shared_ptr 创建:始终使用 std::make_shared<T>()std::shared_ptr<T> 来创建对象。
    • 禁止在栈上创建对象:避免通过在栈上定义对象的方式创建实例。

    示例:

    class MyClass : public std::enable_shared_from_this<MyClass> {
        // ...
    };
    int main() {
        // 错误:在栈上创建对象,shared_from_this () 将失败
        // MyClass obj;
        //auto ptr = obj.shared_from_this (); // 未定义行为
        // 正确:使用 shared_ptr 管理对象
        auto obj = std::make_shared<MyClass>();
        auto ptr = obj->shared_from_this(); // 安全
    }
  2. 在构造函数或析构函数中调用 shared_from_this()
    问题描述:在对象的构造函数或析构函数中调用 shared_from_this() 是不安全的,因为此时对象可能尚未完全构造或已开始析构, shared_from_this() 将导致未定义行为。

    解决方法:

    • 避免在构造函数或析构函数中使用 shared_from_this
    • 避免在构造函数中开启可能会调用 shared_from_this 的线程。

    示例:

    class MyClass : public std::enable_shared_from_this<MyClass> {
    public:
        MyClass() {
            // 错误:在构造函数中使用 shared_from_this ()
            //auto self = shared_from_this (); // 未定义行为
            // 错误:在构造函数中开启会调用 shared_from_this () 的线程
            // std::thread([this](){ RegisterCallback(); }).detach();
        }
        ~MyClass() {
            // 错误:在析构函数中使用 shared_from_this ()
            //auto self = shared_from_this (); // 未定义行为
        }
        void RegisterCallback() {
            auto self = shared_from_this(); // 正确
            // 其它操作
        }
    };
  3. 避免手动使用 std::shared_ptr(this)
    问题描述:在类内部通过 std::shared_ptr(this) 手动构造 shared_ptr ,会导致多个 shared_ptr 分别管理同一对象,引用计数不一致,最终可能导致对象被多次删除。

    解决方法:

    • 使用 shared_from_this () 获取 shared_ptr,确保所有 shared_ptr 共享同一个引用计数。

    示例:

    class MyClass : public std::enable_shared_from_this<MyClass> {
    public:
        void StartAsyncOperation() {
            // 错误:手动构造 shared_ptr
            //auto self = std::shared_ptr<MyClass>(this); // 不安全
            // 正确:使用 shared_from_this ()
            auto self = shared_from_this();
            // 使用 self 进行异步操作
        }
    };
  4. 循环引用导致内存泄漏
    问题描述:当对象之间互相持有 std::shared_ptr,可能形成循环引用,导致内存无法释放。

    解决方法:

    • 使用 std::weak_ptr 打破循环引用:在一方持有另一方的弱引用,防止引用计数无法归零。

    示例:

    class Child;
    class Parent : public std::enable_shared_from_this<Parent> {
    public:
        std::shared_ptr<Child> child;
    };
    class Child {
    public:
        std::weak_ptr<Parent> parent; // 使用 weak_ptr 打破循环
    };
    void createFamily() {
        auto parent = std::make_shared<Parent>();
        auto child = std::make_shared<Child>();
        parent->child = child;
        child->parent = parent;
        // 当 parent 和 child 超出作用域时,内存可以正确释放
    }

# 小结

在复杂的编程场景中,正确使用 std::enable_shared_from_thisshared_from_this 可以有效地管理对象的生命周期,防止内存泄漏和悬空指针等问题。然而,需要注意避免一些常见的陷阱,如对象未由 std::shared_ptr 管理、在构造函数或析构函数中调用 shared_from_this 等。

# 提升代码质量和可维护性

  1. 明确对象的所有权
  • 谁创建,谁负责管理: 确保对象的创建者负责对象的生命周期管理。
  • 减少隐式依赖: 避免隐藏的共享,使对象关系清晰明了。
  • 只有真正需要控制生命周期的类才需要持有 shared_ptr 对象,其余一般建议 weak_ptr。
  1. 合理使用智能指针
  • 选择合适的智能指针类型: 根据需求选择 std::shared_ptr、std::unique_ptr、std::weak_ptr。
  • 避免过度使用 shared_ptr: 不要为了方便而滥用 shared_ptr,增加不必要的性能开销。
  1. 加强代码的可读性
  • 清晰的命名和注释: 让代码自解释,便于他人理解。
  • 模块化设计: 将相关的功能封装在类或模块中,提高代码的复用性。

# enable_shared_from_this 源码

下面是 MSVCenable_shared_from_this 实现,注释掉了无关紧要的部分细节。

template <class _Ty>
class enable_shared_from_this { // provide member functions that create shared_ptr to this
public:
    shared_ptr<_Ty> shared_from_this() {
        return shared_ptr<_Ty>(_Wptr);
    }
    // shared_ptr<const _Ty> shared_from_this() const {
    //     return shared_ptr<const _Ty>(_Wptr);
    // }
    weak_ptr<_Ty> weak_from_this() noexcept {
        return _Wptr;
    }
    // weak_ptr<const _Ty> weak_from_this() const noexcept {
    //     return _Wptr;
    // }
protected:
    constexpr enable_shared_from_this() noexcept : _Wptr() {}
    // enable_shared_from_this(const enable_shared_from_this&) noexcept : _Wptr() {
    //     // construct (must value-initialize _Wptr)
    // }
    // enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept { // assign (must not change _Wptr)
    //     return *this;
    // }
    // ~enable_shared_from_this() = default;
private:
    template <class _Yty>
    friend class shared_ptr;
    mutable weak_ptr<_Ty> _Wptr;
};

通过编码可以看到 enable_shared_from_this 只是保存了一个 weak_ptr 成员,每次调用 shared_from_this 时,通过 weak_ptr 构造出一份 shared_ptr ,从而达到从当前对象返回 shared_ptr 的作用。

# 总结

std::enable_shared_from_this 和 shared_from_this 是现代 C++ 中强大的工具,能够在复杂的异步和多线程环境中安全地管理对象的生命周期。通过实际项目中的应用案例,我们可以看到它们在网络编程、GUI 开发、分布式系统等领域的广泛应用。

在实践中,灵活地运用 shared_from_this,结合设计模式和异步编程框架,可以大大提升代码的质量和可维护性。同时,需要遵循最佳实践,避免常见的陷阱,确保程序的健壮性。