(C++)从this构造shared_ptr导致多控制块的处理

复制代码
class MyClass {
public:
    void share() {
        std::shared_ptr<MyClass> sp(this);   // 危险!
    }
};

如果对象已经被shared_pttr管理,则会创建第二个独立控制块,导致双重删除。

正确做法:继承自 std::enable_shared_from_this<MyClass>,然后使用 shared_from_this()

std::enable_shared_from_this<MyClass>

std::enable_shared_from_this<T> 是一个辅助基类模板,它允许一个被 std::shared_ptr 管理的对象安全地生成指向自身的 shared_ptr

当你有一个对象当前正被 shared_ptr 管理,并且你需要在成员函数中返回一个指向 thisshared_ptr 时,不能 直接 return std::shared_ptr<T>(this)

这样做会从同一个原始指针创建出多个独立的引用计数控制块,导致对象被重复删除(未定义行为)。

cpp 复制代码
class Bad {
public:
    std::shared_ptr<Bad> getptr() {
        return std::shared_ptr<Bad>(this);   // 危险!
    }
};

auto p1 = std::make_shared<Bad>();
auto p2 = p1->getptr();   // p1 和 p2 有不同的控制块,双重删除

enable_shared_from_this 提供了安全的机制,从已存在的 shared_ptr 控制块中获取额外的 shared_ptr,而不会创建新控制块。

用法

  1. 让你的类 MyClass 公有继承 std::enable_shared_from_this<MyClass>
  2. 在需要返回 shared_ptr 指向 this 的地方,调用 shared_from_this()
cpp 复制代码
#include <memory>
#include <iostream>

class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    std::shared_ptr<MyClass> getShared() {
        return shared_from_this();   // 安全
    }

    void print() {
        std::cout << "MyClass object" << std::endl;
    }
};

int main() {
    auto sp1 = std::make_shared<MyClass>();
    auto sp2 = sp1->getShared();   // sp2 和 sp1 共享同一控制块
    std::cout << sp1.use_count() << std::endl; // 2
    return 0;
}

原理

  • enable_shared_from_this 内部保存了一个 weak_ptr<MyClass>,它指向自身的控制块。

  • 当对象第一次被 shared_ptr 管理时(例如通过 std::make_sharedstd::shared_ptr<MyClass>(new MyClass)),该 weak_ptr 会被初始化为指向该控制块。

  • 调用 shared_from_this() 相当于从那个内部的 weak_ptr 构造一个新的 shared_ptr,因此引用计数增加,但控制块唯一。

前置条件

对象必须已经被shared_ptr管理

调用 shared_from_this() 之前,必须确保 当前对象已经被至少一个 shared_ptr 所拥有,否则抛出 std::bad_weak_ptr 异常。

cpp 复制代码
MyClass obj;                     // 栈上对象,未被 shared_ptr 管理
auto sp = obj.shared_from_this(); // 抛出 std::bad_weak_ptr

使用场景

  • 异步回调:需要将当前对象自身传递给异步任务,保证对象生命周期延长至任务完成。

  • 工厂模式 :工厂方法返回 shared_ptr,并且对象内部需要返回自己的 shared_ptr

  • 观察者模式 :被观察者需要将自己的 shared_ptr 注册给观察者。

  • 对象池 :对象需要将自己的 shared_ptr 交给外部使用。

cpp 复制代码
// 异步回调示例
class AsyncTask : public std::enable_shared_from_this<AsyncTask> {
public:
    void start() {
        auto self = shared_from_this();   // 保持对象存活
        std::thread([self]() {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            self->onComplete();
        }).detach();
    }
    void onComplete() { /* 处理完成 */ }
};

注意事项

  • 不要在构造函数中调用 shared_from_this() 。此时对象尚未被 shared_ptr 接管,内部的 weak_ptr 未初始化,会抛出异常。
  • 避免多次继承 enable_shared_from_this。 只能继承一次,且通常用于最派生类。多重继承时需小心虚继承。
  • 性能开销 内部存储一个 weak_ptr,会略微增大对象大小(通常一个指针大小),但开销很小。
  • std::make_shared 兼容make_shared 创建的对象可以正常使用 shared_from_this
  • 不能用于 unique_ptr 管理的对象 。因为 unique_ptr 不维护引用计数控制块,没有对应的 weak_ptr

继承但未通过 shared_ptr 管理的自动检查的示例

设计层面强制对象只能通过 shared_ptr 创建。将构造函数设为私有,并提供静态工厂方法返回 shared_ptr。这样任何对象必然被 shared_ptr 管理,无需额外检查。

cpp 复制代码
#include <memory>

class MyClass : public std::enable_shared_from_this<MyClass> {
private:
    MyClass() = default;                     // 私有构造
public:
    static std::shared_ptr<MyClass> create() {
        return std::shared_ptr<MyClass>(new MyClass());
    }

    void doSomething() {
        auto sp = shared_from_this();        // 安全,因为对象肯定在 shared_ptr 中
    }
};

int main() {
    auto obj = MyClass::create();            // 只能用 factory
    obj->doSomething();
    // MyClass obj2;                         // 错误:构造函数私有
    return 0;
}

但此时对象不能分配在栈上或作为成员

相关推荐
程与留12 小时前
15_国际化和本地化:tr()、ts 文件、QM 文件、多语言切换
c++·qt
程与留14 小时前
14_Qt 样式表(QSS)入门(语法、选择器、美化实战)
c++·qt
欧特克_Glodon1 天前
OpenCV计算机视觉开发入门与实践<二十七>:图像分割概述
c++·人工智能·opencv·计算机视觉
蒸蒸yyyyzwd1 天前
cpp 选手秋招学习笔记 day21
c++·面试·八股
「QT(C++)开发工程师」1 天前
C++ auto 用法详解
开发语言·c++
OPEN-F1 天前
C++STL教程:容器适配器与实用工具
开发语言·c++
OPEN-F1 天前
C++模板教程:变参模板、折叠表达式与SFINAE
java·开发语言·c++
有点。1 天前
C++二叉搜索树进阶
开发语言·c++
HugoStudio_SWAN1 天前
【擦除重绘】C++ 控制台动画:弹跳 Logo DVD 屏保效果
开发语言·c++·学习·程序人生
kyle~1 天前
C++_STL---迭代器失效
开发语言·c++