深入剖析make_shared与enable_shared_from_this的原理与避坑指南

前言

C++11引入的智能指针极大地简化了内存管理,但其中一些高级特性如std::make_sharedstd::enable_shared_from_this在使用中存在不少陷阱。本文将从底层原理出发,结合实际案例,深入分析这些特性并提供实用的避坑指南。

一、make_shared:效率与代价的权衡

1.1 内存分配机制对比

传统shared_ptr构造:两次内存分配

std::shared_ptr<MyClass> ptr(new MyClass);
  • 第一次分配:为MyClass对象分配内存

  • 第二次分配:为控制块(引用计数、删除器等)分配内存

make_shared:一次内存分配

auto ptr = std::make_shared<MyClass>();
  • 单次分配:申请一块足够大的内存,同时容纳控制块和被管理对象

1.2 性能优势

// 性能测试对比
auto start1 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 100000; ++i) {
    std::shared_ptr<MyClass> ptr(new MyClass);
}
auto end1 = std::chrono::high_resolution_clock::now();

auto start2 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 100000; ++i) {
    auto ptr = std::make_shared<MyClass>();
}
au
<think>嗯,用户想了解std::enable_shared_from_this的作用。从用户的问题来看,ta可能正在学习C++智能指针相关的内容,特别是shared_ptr的使用场景。用户提供的三个引用都指向了同一个核心概念:这个类模板用于安全获取对象自身的shared_ptr。 用户可能已经遇到过这样的场景:在一个由shared_ptr管理的对象内部,如果直接用this指针创建新的shared_ptr会导致双重管理的问题。这时候就需要enable_shared_from_this来解决。ta的提问很精准,说明对智能指针有一定基础认知,但可能在实际编码中遇到了具体问题。 从引用材料可以归纳出三个关键点:首先是共享所有权的需求,当对象需要被多个智能指针引用时;其次是生命周期安全性,免悬空指针;最后是使用场景限制,必须在已有shared_ptr的情况下使用。特别要注意的是引用3提到的崩溃案例,这是用户在实际开发中最可能踩到的。 在解释时需要强调两个技术细节:一是为什么不能用this直接构造shared_ptr(因为会创建新的控制块),二是shared_from_this()调用的前置条件(对象必须已被shared_ptr管理)。用户可能更关心实际用法而非底层实现,所以用代码示例展示正确用法和错误用法的对比会更直观。 考虑到用户的学习阶段,补充的“相关问题”设计了梯度:从核心原理(weak_ptr作用)到使用注意事项(构造函数限制),再到应用场景(异步回调)。这样用户可以根据兴趣深入。特别提醒weak_ptr的作用很重要,因为这是enable_shared_from_this实现安全性的关键机制。 </think> `std::enable_shared_from_this` 是 C++ 标准库中一个**关键的类模板**,其主要作用是为一个对象提供一种**安全的方式**从其成员函数内部获取指向它自身的 `std::shared_ptr`。这解决了直接使用 `this` 指针创建新 `shared_ptr` 时可能导致的内存管理错误和未定义行为。 它的核心作用和意义如下: 1. **免双重控制块未定义行为:** * 当一个对象已经由 `std::shared_ptr` 管理时,它内部有一个之关联的**控制块**(包含引用计数等元数据)。 * 如果在该对象的成员函数中**直接使用 `this` 指针构造一个新的 `std::shared_ptr`**(例如 `std::shared_ptr<MyClass>(this)`),这会导致为该**同一个对象**创建**第二个独立的控制块**。 * 结果是多个控制块都试图管理同一个对象的生命周期。当其中一个 `shared_ptr` 的引用计数归零并销毁对象时,其他控制块并不知道,它们持有的指针就变成了**悬垂指针**(dangling pointer)。后续再通过其他 `shared_ptr` 访问对象或其析构函数被再次调用都会导致严重的**未定义行为**(通常表现为程序崩溃)。[^1][^3] * `enable_shared_from_this` 提供的 `shared_from_this()` 成员函数能确保返回的 `shared_ptr` **共享同一个控制块**,从而免了双重控制块的问题。[^1][^3] 2. **安全地共享对象所有权:** * 当对象需要在其成员函数内部(例如,在一个回调函数、事件处理器或需要将自身传递给其他需要 `shared_ptr` 参数的函数中)**将自身传递给其他代码**,并要求该代码也持有其所有权(即延长其生命周期)时,`shared_from_this()` 是唯一安全的方法。 * 它允许对象**安全地参共享所有权语义**,即使是在对象自身的方法内部发起这种共享。[^1] 3. **实现原理简述:** * 类需要**公有继承**自 `std::enable_shared_from_this<T>`(其中 `T` 是该类自身的类型)。 * `enable_shared_from_this` 内部通常持有一个指向 `this` 的 **`weak_ptr`**(弱引用)。 * 当使用 `std::make_shared<T>()` 或 `std::shared_ptr<T>(new T)` 创建一个 `shared_ptr` 管理该对象时,`shared_ptr` 的构造函数会**检测**到该对象继承自 `enable_shared_from_this`,并自动**初始化**其内部的 `weak_ptr`,使其指向这个新创建的 `shared_ptr` 所拥有的控制块。[^2] * 调用 `shared_from_this()` 时,它会**锁定**(`lock()`)内部存储的这个 `weak_ptr`。如果对象当前确实由一个 `shared_ptr` 管理(即 `weak_ptr` 尚未过期),则返回一个共享同一控制块的 `shared_ptr`;否则,抛出 `std::bad_weak_ptr` 异常。[^3] **关键使用要点注意事项:** * **必须在 `shared_ptr` 管理下使用:** `shared_from_this()` **只能**在对象**已经被一个 `std::shared_ptr` 管理**之后调用。在构造函数、析构函数中调用,或者在对象位于栈上或由普通指针管理时调用,都会导致未定义行为(通常是抛出 `std::bad_weak_ptr` 异常)。 * **公有继承:** 类必须**公有继承**自 `std::enable_shared_from_this<T>`。 * **免在构造函数中调用:** 对象构造时,管理它的 `shared_ptr` 可能还未设置好内部的 `weak_ptr`。 * **线程安全:** `shared_from_this()` 的调用本身是线程安全的(标准库保证其实现是线程安全的),但对象本身的状态访问仍需用户自己同步。 **示例说明:** ```cpp #include <memory> #include <iostream> class Good : public std::enable_shared_from_this<Good> { public: std::shared_ptr<Good> getptr() { return shared_from_this(); // 安全!返回共享同一控制块的 shared_ptr } ~Good() { std::cout << "Good destroyed\n"; } }; class Bad { public: std::shared_ptr<Bad> getptr() { return std::shared_ptr<Bad>(this); // 危险!可能导致双重控制块 } ~Bad() { std::cout << "Bad destroyed\n"; } }; int main() { // 正确使用 enable_shared_from_this std::shared_ptr<Good> gp1 = std::make_shared<Good>(); std::shared_ptr<Good> gp2 = gp1->getptr(); // gp1 和 gp2 共享同一控制块 std::cout << "gp2.use_count() = " << gp2.use_count() << '\n'; // 输出 2 // 错误用法:直接使用 this 构造 shared_ptr (未继承 enable_shared_from_this) // 下面的代码在离开作用域时会导致双重析构(未定义行为,通常是崩溃) // std::shared_ptr<Bad> bp1 = std::make_shared<Bad>(); // std::shared_ptr<Bad> bp2 = bp1->getptr(); // 创建了第二个控制块! // std::cout << "bp2.use_count() = " << bp2.use_count() << '\n'; // 输出 1 (每个控制块计数都是1) // 当 bp1 和 bp2 销毁时,Bad 对象会被析构两次 -> 崩溃 return 0; } ``` **总结:** `std::enable_shared_from_this` 的核心作用是为**已被 `std::shared_ptr` 管理的对象**提供一种**安全机制**,允许其成员函数**获取一个共享同一控制块的 `shared_ptr` 指向自身**。这解决了直接使用 `this` 创建 `shared_ptr` 导致的**双重控制块问题**和潜在的**未定义行为**,使得在对象内部安全地传递自身所有权成为可能。它是实现基于 `shared_ptr` 的回调、事件处理等涉及自引用共享所有权场景的关键工具。[^1][^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ปรัชญา แค้วคำมูล

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值