C++17 `weak_from_this` 详解:让对象安全地观察自己

前言

在异步任务、定时器、事件监听和网络回调中,经常会遇到这样的问题:

一个对象需要把自己的成员函数交给其他模块稍后执行,但执行回调时,这个对象可能已经被销毁了。

最直觉的写法是捕获 this

cpp 复制代码
executor.post([this] {
    handleResult();
});

代码很短,却隐藏着生命周期风险。

C++11 引入了 std::enable_shared_from_thisshared_from_this(),让对象可以安全取得共享自己的 shared_ptr。不过 shared_from_this() 会增加强引用计数,可能让对象活得过久,甚至形成循环引用。

C++17 因此为 std::enable_shared_from_this 增加了:

cpp 复制代码
weak_from_this()

它允许对象取得观察自己的 weak_ptr,但不主动延长自己的生命周期。

本文不重新讲解智能指针的基础用法,而是围绕下面这条主线展开:

text 复制代码
异步回调为什么不能随便捕获this
    ↓
为什么shared_ptr(this)更加危险
    ↓
C++11的shared_from_this解决了什么
    ↓
shared_from_this为什么有时又太强
    ↓
C++17的weak_from_this怎样解决问题
    ↓
它与控制块、引用计数到底怎样关联
    ↓
构造函数、析构函数、线程和继承中的陷阱

第一章:从一个异步回调场景开始

1.1 对象把任务交给执行器

假设有一个任务执行器:

cpp 复制代码
class Executor {
public:
    void post(std::function<void()> task);
};

post() 不会立刻执行函数,而是先保存任务,稍后在线程池或事件循环中执行。

再定义一个会提交异步任务的类:

cpp 复制代码
class Session {
public:
    explicit Session(Executor& executor)
        : executor_(executor) {}

    void start() {
        executor_.post([this] {
            handleResult();
        });
    }

private:
    void handleResult() {
        // 处理异步结果
    }

    Executor& executor_;
};

表面上看没有问题:Lambda 保存 this,将来通过它调用成员函数。

1.2 真正的问题是时间差

可能出现下面的执行顺序:

text 复制代码
1. 创建Session对象
2. 调用start()提交回调
3. start()返回
4. 外部释放Session对象
5. 执行器开始运行之前保存的回调
6. 回调通过已经失效的this访问对象

第 6 步中的 this 已经成为:

text 复制代码
Dangling Pointer
= 悬空指针

使用悬空指针会产生:

text 复制代码
Undefined Behavior
= 未定义行为

程序可能崩溃,也可能读到错误数据,还可能暂时看似正常,因此特别难排查。

1.3 捕获 this 不会延长生命周期

Lambda 中的:

cpp 复制代码
[this]

只是复制了一个对象地址。它并不知道:

  • 对象由谁拥有;
  • 对象什么时候销毁;
  • 执行回调时对象是否仍然存在。

因此,捕获 this 适用于调用者能够严格保证对象存活的情况;一旦回调可能晚于对象销毁,就必须重新设计生命周期关系。


第二章:直接构造 shared_ptr(this) 为什么更危险

2.1 看似合理的修复

有人会想到:既然裸 this 不管理生命周期,那就在成员函数中把它包装成 shared_ptr

cpp 复制代码
void Session::start() {
    std::shared_ptr<Session> self(this);

    executor_.post([self] {
        self->handleResult();
    });
}

这段代码通常不是修复,而是在制造更严重的问题。

2.2 一个对象被两个控制块管理

假设对象原本这样创建:

cpp 复制代码
auto session =
    std::make_shared<Session>(executor);

此时已经存在一个 shared_ptr 控制块:

text 复制代码
控制块A
    强引用计数
    弱引用计数
    对象销毁信息
        ↓
    Session对象

成员函数再执行:

cpp 复制代码
std::shared_ptr<Session> self(this);

新构造的 shared_ptr 不知道控制块 A 的存在,于是建立控制块 B:

text 复制代码
控制块A ───┐
           ├── 都认为自己负责同一个Session对象
控制块B ───┘

当两个控制块的强引用计数分别归零时,它们都会尝试删除同一个对象,可能导致:

text 复制代码
Double Delete / Double Free
= 重复删除 / 重复释放

2.3 真正需要的是找到原控制块

安全方案不能重新创建一个拥有 this 的控制块,而应该:

找到当前对象原本所属的控制块,再生成一个与原 shared_ptr 共享该控制块的新 shared_ptr

C++11 的 std::enable_shared_from_this 就是为此设计的。


第三章:C++11 的 enable_shared_from_this

3.1 基本写法

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    explicit Session(Executor& executor)
        : executor_(executor) {}

private:
    Executor& executor_;
};

enable_shared_from_this 可以拆开理解:

text 复制代码
enable
    使某种能力可用

shared_from_this
    从this获得共享所有权指针

它是一个类模板,模板参数填写最终需要获得指针的类型:

cpp 复制代码
std::enable_shared_from_this<Session>

这种"派生类把自己作为基类模板参数"的外形和 CRTP 相似:

text 复制代码
CRTP
= Curiously Recurring Template Pattern
= 奇异递归模板模式

不过这里的重点不是静态多态,而是让标准库知道应该生成哪种 shared_ptr<T>weak_ptr<T>

3.2 shared_from_this() 怎样使用

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    explicit Session(Executor& executor)
        : executor_(executor) {}

    void start() {
        auto self = shared_from_this();

        executor_.post([self] {
            self->handleResult();
        });
    }

private:
    void handleResult() {}

    Executor& executor_;
};

使用时,对象必须先由 shared_ptr 管理:

cpp 复制代码
auto session =
    std::make_shared<Session>(executor);

session->start();

shared_from_this() 返回的新指针与 session 共用同一个控制块:

text 复制代码
外部session ──────┐
                  ├── 控制块 ── Session对象
回调捕获的self ──┘

回调持有 self 期间,强引用计数不会归零,因此对象不会被销毁。

3.3 它表达的是"任务必须完成"

捕获强引用:

cpp 复制代码
[self = shared_from_this()] {
    self->handleResult();
}

表达的生命周期语义是:

即使所有外部所有者都放弃对象,只要这个回调还没有销毁,对象就必须继续存活。

对于必须完成的写盘、事务提交或关键收尾任务,这可能正是需要的行为。

但并非所有回调都需要这种强保证。


第四章:C++11 方案仍然存在的局限

这里所说的"局限",不是 shared_ptr 设计错误,而是只有强引用式 shared_from_this() 时,难以自然表达某些生命周期语义。

4.1 不重要的回调也会强行延长对象寿命

假设提交的是界面刷新、状态通知或定时统计任务:对象存在时就执行,对象已经销毁时直接放弃即可。

如果仍然捕获:

cpp 复制代码
auto self = shared_from_this();

那么任务队列会强行保活对象。

text 复制代码
外部已经不再需要Session
    ↓
队列中的回调仍持有shared_ptr
    ↓
Session不能析构
    ↓
必须等回调从队列中执行或删除

队列积压时,对象可能比预期晚很久才释放。

4.2 对象保存回调时可能形成循环引用

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    void installCallback() {
        callback_ =
            [self = shared_from_this()] {
                self->handleResult();
            };
    }

private:
    std::function<void()> callback_;
};

所有权关系变成:

text 复制代码
Session对象
    ↓ 拥有成员
callback_
    ↓ 捕获shared_ptr
Session对象

对象通过自己的成员间接拥有自己,强引用计数无法自然归零。

4.3 shared_from_this() 失败时会抛异常

下面的对象没有被 shared_ptr 管理:

cpp 复制代码
Session session{executor};
session.start();

或者:

cpp 复制代码
auto session =
    std::make_unique<Session>(executor);

session->start();

此时调用:

cpp 复制代码
shared_from_this()

无法找到共享控制块,从 C++17 起明确抛出:

text 复制代码
std::bad_weak_ptr

4.4 构造函数中调用得太早

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    Session() {
        auto self = shared_from_this(); // 错误时机
    }
};

即使外部写的是:

cpp 复制代码
auto session = std::make_shared<Session>();

执行 Session 构造函数时,外层 shared_ptr 还没有完成创建,也还没有把内部观察指针连接到控制块。

所以构造函数中的 shared_from_this() 通常会抛出 std::bad_weak_ptr

4.5 C++11 已经可以手工得到 weak_ptr,但不够直接

C++11 中可以先取得强引用,再转换成弱引用:

cpp 复制代码
std::weak_ptr<Session> weakSelf =
    shared_from_this();

这可以工作,但语义绕了一圈:

text 复制代码
先构造shared_ptr并短暂增加强引用计数
    ↓
再构造weak_ptr
    ↓
临时shared_ptr销毁,强引用计数恢复

更重要的是,如果对象尚未被共享管理,第一步就会抛出异常。

于是 C++17 为这个常见需求增加了直接接口。


第五章:C++17 新增 weak_from_this()

5.1 先澄清版本关系

text 复制代码
C++11
    std::enable_shared_from_this
    shared_from_this()

C++17
    weak_from_this()

因此,下面这句继承本身不是 C++17 才出现的:

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session>

C++17 新增的是通过该基类调用:

cpp 复制代码
weak_from_this()

5.2 基本用法

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    explicit Session(Executor& executor)
        : executor_(executor) {}

    void start() {
        std::weak_ptr<Session> weakSelf =
            weak_from_this();

        executor_.post([weakSelf] {
            if (auto self = weakSelf.lock()) {
                self->handleResult();
            }
        });
    }

private:
    void handleResult() {}

    Executor& executor_;
};

5.3 它表达的是"对象还在就执行"

这段代码的语义是:

text 复制代码
提交任务时
    回调只保存weak_ptr,不拥有对象

执行任务时
    尝试lock()

lock成功
    对象仍然存在
    临时shared_ptr保证本次调用期间对象不被销毁

lock失败
    对象已经销毁
    放弃本次回调

它非常适合:

  • 延迟通知;
  • 非关键定时任务;
  • 事件监听;
  • 可取消的异步操作;
  • 对象销毁后无需继续执行的回调。

5.4 weak_from_this() 不抛出 bad_weak_ptr

它的典型声明可以理解为:

cpp 复制代码
std::weak_ptr<T>
weak_from_this() noexcept;

std::weak_ptr<const T>
weak_from_this() const noexcept;

如果对象尚未连接到共享控制块,它会返回空 weak_ptr,而不是像 shared_from_this() 那样构造强引用失败并抛出 std::bad_weak_ptr

因此:

cpp 复制代码
auto weakSelf = weak_from_this();

if (weakSelf.expired()) {
    // 当前没有可共享的对象所有权
}

可以用于非抛异常式观察。

不过,"不抛异常"不等于"构造函数里得到的弱指针以后会自动生效",后面会专门解释这个陷阱。


第六章:shared_from_thisweak_from_this 怎样选择

选择标准不是"哪一个更新",而是任务需要表达什么生命周期关系。

6.1 必须完成的任务使用强引用

cpp 复制代码
void Session::flush() {
    executor_.post(
        [self = shared_from_this()] {
            self->flushData();
        }
    );
}

含义:

text 复制代码
任务完成之前,Session必须存活。

6.2 对象存在时才执行的任务使用弱引用

cpp 复制代码
void Session::scheduleRefresh() {
    executor_.post(
        [weakSelf = weak_from_this()] {
            if (auto self = weakSelf.lock()) {
                self->refreshStatus();
            }
        }
    );
}

含义:

text 复制代码
Session存在就刷新;已经销毁就取消。

6.3 对照表

需求 推荐方式
回调必须执行完 捕获 shared_from_this()
对象销毁后回调可放弃 捕获 weak_from_this()
对象内部长期保存回调 通常优先 weak_from_this()
回调只在当前同步调用内使用 直接使用引用或 this 也可能足够
对象不由 shared_ptr 管理 不应依赖这一套机制

最重要的问题是:

回调是否应该成为对象的所有者?

如果答案是否定的,就不应该因为写起来方便而捕获强引用。


第七章:不要分开调用 expired()lock()

7.1 有竞争窗口的写法

cpp 复制代码
if (!weakSelf.expired()) {
    auto self = weakSelf.lock();
    self->handleResult();
}

在多线程环境中可能发生:

text 复制代码
线程A:expired()返回false
线程B:释放最后一个shared_ptr,对象销毁
线程A:lock()返回空指针
线程A:解引用空指针

expired() 的结果只代表检查发生的那个瞬间。

7.2 直接检查 lock() 的结果

正确方式:

cpp 复制代码
if (auto self = weakSelf.lock()) {
    self->handleResult();
}

lock() 会尝试原子地取得一个强引用:

text 复制代码
成功
    返回非空shared_ptr
    在self作用域结束前对象保持存活

失败
    返回空shared_ptr
    不访问对象

这解决的是对象生命周期竞争,但不自动保证对象成员数据线程安全。

即使 lock() 成功,多个线程同时修改成员仍然可能需要互斥量、原子变量或其他同步机制。


第八章:构造函数中的关键陷阱

8.1 构造期间内部弱指针还没有连接控制块

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    Session() {
        auto weakSelf = weak_from_this();

        // 此时通常为空
    }
};

weak_from_this() 不会抛异常,但返回的通常是空弱指针。

8.2 提前取得的空 weak_ptr 不会自动更新

下面的写法尤其容易误解:

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    Session()
        : self_(weak_from_this()) {}

private:
    std::weak_ptr<Session> self_;
};

有人可能认为:

text 复制代码
现在self_为空没关系,
等make_shared完成后它会自动连上控制块。

实际并不会。

构造函数中复制出来的 self_ 是一个独立的空 weak_ptr。之后 enable_shared_from_this 内部的观察指针虽然会被初始化,但之前复制出去的空 weak_ptr 不会自动改绑。

8.3 使用创建后初始化

把需要 weak_from_this() 的逻辑放到对象被 shared_ptr 接管之后:

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    explicit Session(Executor& executor)
        : executor_(executor) {}

    void initialize() {
        weakSelf_ = weak_from_this();
    }

private:
    Executor& executor_;
    std::weak_ptr<Session> weakSelf_;
};

auto session =
    std::make_shared<Session>(executor);

session->initialize();

但公开 initialize() 容易被忘记调用。工程中可以通过工厂函数封装两阶段初始化。


第九章:用工厂函数保证正确初始化顺序

9.1 工厂负责完成两件事

text 复制代码
第一步:让shared_ptr接管对象
第二步:执行需要weak_from_this的初始化

示例:

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
public:
    static std::shared_ptr<Session>
    create(Executor& executor) {
        struct MakeSharedEnabler
            : public Session {
            explicit MakeSharedEnabler(
                Executor& executor
            )
                : Session(executor) {}
        };

        auto session =
            std::make_shared<MakeSharedEnabler>(
                executor
            );

        session->initialize();
        return session;
    }

    void start() {
        executor_.post(
            [weakSelf = weak_from_this()] {
                if (auto self = weakSelf.lock()) {
                    self->handleResult();
                }
            }
        );
    }

protected:
    explicit Session(Executor& executor)
        : executor_(executor) {}

private:
    void initialize() {
        // 此时已经存在shared_ptr控制块
        weakSelf_ = weak_from_this();
    }

    void handleResult() {}

    Executor& executor_;
    std::weak_ptr<Session> weakSelf_;
};

使用者只能通过:

cpp 复制代码
auto session = Session::create(executor);

拿到对象,工厂保证初始化顺序正确。

9.2 为什么使用 MakeSharedEnabler

如果构造函数是 private 或 protected,std::make_shared<Session> 的标准库内部代码通常没有访问权限。

局部辅助类公开转发构造,再由 make_shared 创建辅助类,可以继续获得合并分配等优势,同时限制外部随意构造 Session

这种写法不是 weak_from_this() 的硬性要求,只是封装两阶段初始化的一种常见工程手法。


第十章:enable_shared_from_this 的内部原理

10.1 简化模型

标准库的具体实现不完全相同,但可以把它粗略理解为:

cpp 复制代码
template<class T>
class enable_shared_from_this {
public:
    std::shared_ptr<T> shared_from_this() {
        return std::shared_ptr<T>(weakThis_);
    }

    std::weak_ptr<T>
    weak_from_this() noexcept {
        return weakThis_;
    }

protected:
    constexpr enable_shared_from_this()
        noexcept = default;

private:
    mutable std::weak_ptr<T> weakThis_;
};

真实标准库还会包含 const 重载、友元关系和实现细节。

10.2 谁负责初始化内部的 weakThis_

不是对象构造函数自己初始化,而是第一个正确接管对象的 shared_ptr 完成连接。

cpp 复制代码
auto session =
    std::make_shared<Session>(executor);

shared_ptr 创建过程中会检测:

text 复制代码
Session是否拥有一个公开且无歧义的
enable_shared_from_this<Session>基类?

如果满足条件,就把基类内部的观察指针连接到新控制块:

text 复制代码
shared_ptr<Session>
        │
        ▼
     控制块
        ▲
        │
enable_shared_from_this内部weakThis_

10.3 两个成员函数只是从内部弱指针生成不同结果

text 复制代码
shared_from_this()
    从内部weak_ptr构造shared_ptr
    成功时增加强引用计数
    没有控制块时抛bad_weak_ptr

weak_from_this()
    复制内部weak_ptr
    不增加强引用计数
    没有控制块时得到空weak_ptr

10.4 为什么基类通常必须 public 继承

正确写法:

cpp 复制代码
class Session
    : public std::enable_shared_from_this<Session> {
};

错误倾向:

cpp 复制代码
class Session
    : private std::enable_shared_from_this<Session> {
};

标准库需要从外部检测并访问这个基类。private 继承可能使这条转换不可访问,导致内部弱指针没有按预期连接控制块。

因此应使用公开、无歧义继承。


第十一章:继承层次中的类型问题

11.1 基类继承后,返回的是基类指针

cpp 复制代码
class Base
    : public std::enable_shared_from_this<Base> {
};

class Derived : public Base {
};

Derived 对象中调用:

cpp 复制代码
shared_from_this()

返回类型仍然是:

cpp 复制代码
std::shared_ptr<Base>

因为模板参数写的是 Base

如果确实需要 shared_ptr<Derived>,可以在确认真实类型后转换:

cpp 复制代码
auto derived =
    std::dynamic_pointer_cast<Derived>(
        shared_from_this()
    );

或者让真正需要"获得自己"的最终类型继承相应的 enable_shared_from_this<Derived>。不要在复杂继承体系中重复放置多个有歧义的 enable_shared_from_this 基类。

11.2 不要让同一对象产生多个控制块

无论是否继承 enable_shared_from_this,下面的根本规则都不能破坏:

同一个动态对象只能由同一套共享所有权控制块负责销毁。

危险示例:

cpp 复制代码
Session* raw = new Session(executor);

std::shared_ptr<Session> first(raw);
std::shared_ptr<Session> second(raw);

两个从同一裸指针分别构造的 shared_ptr 不会自动合并控制块。

enable_shared_from_this 能让对象找到已经建立的控制块,但不能修复人为创建多个独立控制块的错误。


第十二章:析构期间不要试图"复活"对象

12.1 析构开始意味着强所有权已经结束

对象析构通常发生在最后一个强引用释放之后。

此时即使能够取得内部弱指针:

cpp 复制代码
auto weakSelf = weak_from_this();

它的 lock() 通常也不能重新得到有效强引用,因为强引用计数已经归零,对象的销毁已经开始。

12.2 不要在析构函数中注册新的自身回调

cpp 复制代码
Session::~Session() {
    executor_.post(
        [weakSelf = weak_from_this()] {
            // 不应期待以后还能恢复Session
        }
    );
}

析构函数应该完成资源释放和注销,不应该试图通过新的共享所有权让对象复活。

如果需要异步收尾,应在对象仍然有效时显式启动,并用清晰的状态机或外部管理器协调。


第十三章:线程安全到底保证到哪一层

13.1 lock() 保护的是生命周期

cpp 复制代码
if (auto self = weakSelf.lock()) {
    self->handleResult();
}

一旦 lock() 成功,self 在当前作用域内持有强引用,因此对象不会在函数执行一半时被其他线程销毁。

13.2 它不保护对象的业务数据

下面两个线程都可能成功取得 self

cpp 复制代码
// 线程A
if (auto self = weakSelf.lock()) {
    self->updateState();
}

// 线程B
if (auto self = weakSelf.lock()) {
    self->updateState();
}

如果 updateState() 修改共享成员,仍然可能发生数据竞争。

需要另外使用:

  • std::mutex
  • std::atomic
  • 串行执行器;
  • Actor 模型;
  • 明确的线程归属约束。

可以记住:

text 复制代码
weak_ptr::lock()
    解决"对象还活着吗"

mutex或其他同步机制
    解决"多个线程能同时改数据吗"

第十四章:完整的 C++17 示例

下面用一个简单任务队列展示完整流程。示例故意把任务延迟到外部主动执行,以便观察对象销毁后的行为。

cpp 复制代码
#include <functional>
#include <iostream>
#include <memory>
#include <queue>
#include <utility>

class Executor {
public:
    void post(std::function<void()> task) {
        tasks_.push(std::move(task));
    }

    void runAll() {
        while (!tasks_.empty()) {
            auto task = std::move(tasks_.front());
            tasks_.pop();
            task();
        }
    }

private:
    std::queue<std::function<void()>> tasks_;
};

class Session
    : public std::enable_shared_from_this<Session> {
public:
    explicit Session(Executor& executor)
        : executor_(executor) {
        std::cout << "Session构造\n";
    }

    ~Session() {
        std::cout << "Session析构\n";
    }

    void postOptionalTask() {
        executor_.post(
            [weakSelf = weak_from_this()] {
                if (auto self = weakSelf.lock()) {
                    self->handleResult();
                } else {
                    std::cout
                        << "对象已销毁,取消任务\n";
                }
            }
        );
    }

    void postRequiredTask() {
        executor_.post(
            [self = shared_from_this()] {
                self->handleResult();
            }
        );
    }

private:
    void handleResult() {
        std::cout << "执行Session任务\n";
    }

    Executor& executor_;
};

int main() {
    Executor executor;

    {
        auto session =
            std::make_shared<Session>(executor);

        session->postOptionalTask();
    }

    std::cout << "运行可取消任务:\n";
    executor.runAll();

    {
        auto session =
            std::make_shared<Session>(executor);

        session->postRequiredTask();
    }

    std::cout << "运行必须完成的任务:\n";
    executor.runAll();
}

第一段使用 weak_from_this()

text 复制代码
离开作用域
    ↓
外部shared_ptr销毁
    ↓
回调只有weak_ptr,不拥有对象
    ↓
Session立即析构
    ↓
执行任务时lock失败,取消任务

第二段使用 shared_from_this()

text 复制代码
离开作用域
    ↓
外部shared_ptr销毁
    ↓
回调仍持有shared_ptr
    ↓
Session继续存活
    ↓
任务执行完成,回调销毁
    ↓
最后一个shared_ptr释放,Session析构

两个接口没有绝对的优劣,它们表达的是两种不同生命周期策略。


第十五章:常见错误清单

15.1 错把整个特性当成 C++17 新增

text 复制代码
enable_shared_from_this和shared_from_this
    C++11已有

weak_from_this
    C++17新增

15.2 使用 shared_ptr(this)

它可能创建第二个控制块,导致重复释放。应使用 shared_from_this() 获取共享原控制块的指针。

15.3 对栈对象调用 shared_from_this()

cpp 复制代码
Session session{executor};
session.shared_from_this();

对象没有共享控制块,会失败并抛出 std::bad_weak_ptr

15.4 在构造函数中调用 shared_from_this()

此时控制块尚未完成连接,调用时机过早。

15.5 认为构造函数中取得的 weak_ptr 以后会自动有效

构造期间复制出的空 weak_ptr 不会在外层 shared_ptr 创建完成后自动改绑。应进行创建后初始化。

15.6 忘记检查 lock() 结果

cpp 复制代码
auto self = weakSelf.lock();
self->run(); // self可能为空

应写成:

cpp 复制代码
if (auto self = weakSelf.lock()) {
    self->run();
}

15.7 先检查 expired() 再无条件使用 lock()

两步之间存在竞争窗口。直接检查 lock() 返回值。

15.8 认为 lock() 能保证成员线程安全

它只保证对象生命周期,不替代互斥量和线程同步。

15.9 使用 private 继承

cpp 复制代码
class Session
    : private std::enable_shared_from_this<Session> {
};

可能阻止标准库正确连接内部观察指针。应使用 public、无歧义继承。

15.10 无条件使用弱引用

如果任务必须完成,弱引用可能让任务在对象销毁后静默取消。应该先决定生命周期语义,再选择强引用还是弱引用。


第十六章:最终总结

std::enable_shared_from_this<T> 解决的核心问题是:

已经由 shared_ptr 管理的对象,怎样在成员函数中找到原来的控制块,而不是围绕 this 创建第二个控制块。

C++11 提供:

cpp 复制代码
shared_from_this()

它生成共享同一控制块的强引用,适用于:

text 复制代码
任务必须完成
回调期间必须保活对象

C++17 新增:

cpp 复制代码
weak_from_this()

它生成观察同一控制块的弱引用,适用于:

text 复制代码
对象存在就执行
对象销毁就取消
不希望回调延长对象寿命
希望避免自身循环引用

把完整机制连起来:

text 复制代码
类public继承enable_shared_from_this<T>
    ↓
shared_ptr第一次正确接管对象
    ↓
标准库把内部weak_ptr连接到控制块
    ↓
shared_from_this()从内部weak_ptr取得强引用
    ↓
weak_from_this()复制内部weak_ptr作为观察者
    ↓
回调执行时通过lock()安全确认对象是否仍然存在

最终需要记住五条规则:

  1. 不要使用 shared_ptr(this) 创建自身共享指针。
  2. shared_from_this() 会保活对象,weak_from_this() 不会。
  3. 对象必须先被 shared_ptr 正确接管,内部观察指针才会连接控制块。
  4. 构造函数中调用得太早,必要时使用工厂和创建后初始化。
  5. 多线程回调中直接检查 lock() 结果,但还要另外保护业务数据。

真正重要的不是机械地把所有回调都换成 weak_from_this(),而是先回答:

这个回调应不应该拥有对象?

答案决定了应该使用强引用保活,还是使用弱引用观察。

相关推荐
June`4 小时前
warp shuffle指令
c++·人工智能·算法·cuda
乱七八糟的屋子4 小时前
【C++数值计算】NumCpp超详细入门教程(纯正Numpy语法、零学习成本、C++首选科学计算库、性能压测对比)
c++·数值计算·numpy·高性能计算·科学计算·ndarray
choumin4 小时前
C程序能调用extern “C“声明的带默认参数的C++函数吗?
c语言·c++·extern c·默认参数
AmyLin_20015 小时前
PDF 色彩保真工程实践【3】上手实践:工程结构、依赖集成与一键构建运行
c++·visualstudio·pdf·zlib·vcpkg·libtiff·cmyk
并不喜欢吃鱼5 小时前
从零开始 C++------ 十六.深度拆解 C++ 智能指针:unique_ptr/shared_ptr/weak_ptr 底层模拟、内存泄漏根治
开发语言·c++
不相心 -w-5 小时前
Cmake的基础用法
linux·开发语言·c++
撑伞的鱼99375 小时前
C++开发用什么AI编程工具效果好?2026年实测横评(附选型指南)
开发语言·c++·ai编程·cursor
j7~6 小时前
【Linux】二十八.线程篇五《Linux多线程编程:线程同步之条件变量》---详解
linux·运维·c++·学习·条件变量·线程同步
王老师青少年编程6 小时前
2026年全国青少年信息素养大赛算法应用主题赛C++赛项【决赛】模拟卷3:文末附答案
c++·答案·模拟题·2026年·青少年信息素养大赛·算法应用主题赛·决赛