首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >彻底理解智能指针:shared_ptr

彻底理解智能指针:shared_ptr

作者头像
早起的鸟儿有虫吃
发布2025-11-20 15:49:25
发布2025-11-20 15:49:25
630
举报

21天冲刺大厂面试·第四天 | C++11深度探索

你可能不知道

C++11 发布于 2011年8月,到2025年已经 14年 了。

14年,一个技术的轮回

坦白说,这些新特性在我日常工作中几乎用不到。

为什么?现实很骨感:

(1) 应用场景的门槛

C++做界面开发:MFC、QT、游戏客户端、OpenCV图像处理...

如果进不了相关公司,根本没人给你实践的机会

(2) 基础设施的壁垒

C/C++基础设施开发:分布式数据库、存储系统、中间件...

如果进不了相关公司,根本没人给你实践的机会

即使有开源项目,个人也难有合适的环境练习

像TiFlash、OceanBase、Ceph这些项目,平时根本用不到

没有实际需求,很难静下心来深入研究

(3) 唯一可行的路径

只剩下在普通笔记本上,建立工程,用std标准库写demo练习

对比其他同学怎么做的,

通过考研,面试 进入行业公司,然后直接瞬间进入数据库,os ai 行业 然后再开发相关项目。

你看出来了吗?

最关键的 并不是 c++本身,明白了,进入相关行业,

我为什么痴迷于写demo、研究原理?

都是被现实逼的。

哪怕进入公司 讨论 方案设计,bug 定位解决,没有 人关系 c++新特性,

所以,我能做的就是把能做的事情做好:

一个笔记本,建立项目工程,写一个 cpp 文件,std 标准库,写 demo,git、cmake

把C++11新特性研究透彻

今天 就解决掉最 不关键事情,研究无用事情。

Part 1 面试官提问:

智能指针 shared_ptr 使用场景(其他指针无法代替)

boost::asio::async_write【需要考虑什么事情】

循环引用

Part 2 我的回答

本文举例代码均来 3FS 等事项

2.2 std::shared_ptr

https://en.cppreference.com/w/cpp/memory/shared_ptr.html

std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer.

The object is destroyed and its memory deallocated when either of the following happens: the last remaining shared_ptr owning the object is destroyed

根据例子 ,被多个使用者使用,不清楚,不清楚使用你这生命周期(多线程)

还有更多例子吗?

场景 1:C++11新特性之十:enable_shared_from_this

因为在异步调用中,存在一个保活机制,异步函数执行的时间点我们是无法确定的,然而异步函数可能会使用到异步调用之前就存在的变量。

为了保证该变量在异步函数执期间一直有效,我们可以传递一个指向自身的share_ptr给异步函数,这样在异步函数执行期间share_ptr所管理的对象就不会析构,

所使用的变量也会一直有效了(保活)。

场景:异步网络发送

你有一个网络连接类 Transport,用来异步发送数据。 异步操作意味着:函数返回后还没发送完,稍后某个线程会回调

没有 shared_from_this() 的危险写法:
代码语言:javascript
复制
class Transport {
public:
    void sendAsync() {
        ioWorker_.asyncWrite([this]() {
            this->handleWriteComplete();
        });
    }
};

现在假设这样使用:

代码语言:javascript
复制
auto t = std::make_shared<Transport>();
t->sendAsync();      // 发起异步操作
t.reset();           // 调用者提前释放(连接关闭、对象析构)

此时会发生什么?

sendAsync() 提交了异步任务,但任务可能几百毫秒后才真正执行。

t.reset() 导致 Transport 对象被销毁。

等到异步任务线程执行回调时,lambda 里的 this 指针已经悬空

程序访问已销毁的对象 → 未定义行为 / 崩溃

🧠 类似于:

你请别人“稍后”给你发封邮件,但你提前把邮箱注销了,对方再发邮件时就炸了。

✅ 改进:用 shared_from_this() 延长生命周期

代码语言:javascript
复制
class Transport : public std::enable_shared_from_this<Transport> {
public:
    void sendAsync() {
        auto self = shared_from_this(); // 拿到自己的一份共享所有权

        ioWorker_.asyncWrite([self]() {
            self->handleWriteComplete(); // self 保证对象不被提前销毁
        });
    }
};

再执行相同代码:

代码语言:javascript
复制
auto t = std::make_shared<Transport>();
t->sendAsync();  // 发起异步操作
t.reset();       // 调用者释放自己的那份

这次不会崩溃,因为:

sendAsync() 内部的 lambda 捕获了 self(一个 shared_ptr)。

即使外部的 t 已经 reset,self 仍然持有对象。

对象至少活到 asyncWrite 回调执行完毕。

回调执行完 self 被销毁,对象才会真正释放。

代码语言:javascript
复制
class Transport : public std::enable_shared_from_this<Transport> {
public:
    void sendAsync(std::vector<char> data) {
        auto self = shared_from_this();
        ioWorker_.asyncWrite([self, data = std::move(data)]() mutable {
            // 这里 data 和 self 一起活到写完
            self->handleWriteComplete(data);
        });
    }

    void handleWriteComplete(const std::vector<char>& data) {
        std::cout << "Write complete: " << data.size() << " bytes\n";
    }
};

好处:

data 被安全地移动进回调,不会在外部被释放或修改;

self 确保回调中访问对象是安全的;

回调执行完,selfdata 一起销毁。

问题

没用 shared_from_this

用了 shared_from_this

异步回调执行时对象还在吗?

❌ 可能被销毁

✅ 一定还在

访问 this 安全吗?

❌ 不安全(悬空指针)

✅ 安全

对象释放时机

不确定(可能太早)

延迟到回调结束

实现代价

简单

稍复杂但安全

疑问:shared_from_this是如何做到“让一个对象知道自己被shared_ptr` 管着”的

你这个问题问到了核心本质 👏——

shared_from_this 是如何做到“让一个对象知道自己被 shared_ptr 管着”的?

一、根本问题:普通类为什么不行?

看个普通类:

代码语言:javascript
复制
class Foo {
public:
    void bar() {
        auto p = shared_from_this(); // ❌ 编译错误
    }
};

编译就报错:

‘class Foo’ has no member named ‘shared_from_this’

原因: shared_from_this()std::enable_shared_from_this<T> 提供的,普通类根本没有这个机制。

二、原理:enable_shared_from_this 是怎么“绑定”上的?

当你写:

代码语言:javascript
复制
class Foo : public std::enable_shared_from_this<Foo> {
public:
    void bar() {
        auto p = shared_from_this(); // ✅ 可以用了
    }
};

真正的魔法发生在 std::shared_ptr<Foo> 的构造过程

代码语言:javascript
复制
template<class T>
class enable_shared_from_this {
protected:
    mutable std::weak_ptr<T> weak_this_; // 💡 弱引用指针,不影响计数
public:
    shared_ptr<T> shared_from_this() {
        return weak_this_.lock(); // 从弱引用锁出一个强引用
    }
    weak_ptr<T> weak_from_this() noexcept {
        return weak_this_;
    }
};

当你这样创建对象时:

代码语言:javascript
复制
auto p = std::make_shared<Foo>();

shared_ptr 构造函数内部,它会自动检测:

“这个 Foo 有没有继承 enable_shared_from_this<Foo>?”

如果有,它会把自己注册进去:

代码语言:javascript
复制
p->weak_this_ = p; // 内部逻辑:让 enable_shared_from_this 知道自己的 shared_ptr

这样对象内部的 weak_this_ 就能指向自己被管理的那份控制块。

三、调用时的过程

当你调用:

代码语言:javascript
复制
auto p2 = this->shared_from_this();

实际执行的是:

代码语言:javascript
复制
return weak_this_.lock();

lock() 做的事是:

如果对象还活着(控制块的引用计数 > 0),就返回一个新的 shared_ptr

否则返回空(expired)。

🧠 所以:

shared_from_this() 实际上就是 “把自己之前保存的弱引用 weak_ptr 转回一个强引用 shared_ptr”。

要点先说清楚

enable_shared_from_this<T> 内部包含一个 mutable std::weak_ptr<T> weak_this_

当你用 std::make_shared<T>() 或用 std::shared_ptr<T>(new T) 创建 shared_ptr 时,shared_ptr 的实现会检测到对象继承了 enable_shared_from_this<T>,并在构造完成后把刚创建的 shared_ptr 或其控制块写入对象的 weak_this_

因此 shared_from_this() 不是“自己去找”这个 shared_ptr,而是“之前创建 shared_ptr 的代码已经把它塞进来了”

代码语言:javascript
复制

// 当 make_shared 创建对象/控制块时(非常简化的伪流程):
template<class T, class... Args>
std::shared_ptr<T> make_shared(Args&&... args) {
    // 1) 分配控制块(control block)并在其上构造 T(可能在同一块内存)
    control_block* cb = new control_block(...);
    T* obj = new(cb->storage) T(std::forward<Args>(args)...);

    // 2) 创建 shared_ptr 指向 control block(管理引用计数)
    std::shared_ptr<T> sp(cb, obj); // 实际实现更复杂

    // 3) 关键:如果 T 继承 enable_shared_from_this<T>
    //    把当前的 shared_ptr 写回对象的 weak_this_
    //    (这样对象内部就能通过 weak_from_this()/shared_from_this() 得到同一控制块)
    if constexpr (std::is_base_of_v<enable_shared_from_this<T>, T>) {
        obj->weak_this_ = sp; // 这里就是把 shared_ptr 的控制块“挂”到对象上
    }

    // 4) 返回 shared_ptr 给用户
    return sp;
}

https://en.cppreference.com/w/cpp/types/is_base_of.html


std::is_base_of 是 C++ 标准库中的一个类型特性(Type Trait),

用于判断一个类是否是另一个类的基类。

举个完整例子(含控制流程)

代码语言:javascript
复制
#include <memory>
#include <iostream>

struct Foo : public std::enable_shared_from_this<Foo> {
    void show() {
        auto self = shared_from_this();
        std::cout << "shared_from_this(): use_count=" << self.use_count() << "\n";
    }
    ~Foo() { std::cout << "Foo destroyed\n"; }
};

int main() {
    auto p1 = std::make_shared<Foo>();
    std::cout << "p1.use_count=" << p1.use_count() << "\n";
    p1->show(); // 内部再拿一份 shared_ptr
    std::cout << "p1.use_count(after)=" << p1.use_count() << "\n";
}

输出大致是:

代码语言:javascript
复制
p1.use_count=1shared_from_this(): use_count=2p1.use_count(after)=1Foo destroyed

说明:

std::make_shared<Foo>() 创建对象时,weak_this_ 被自动设置。

shared_from_this()weak_ptr 锁出新 shared_ptr,引用计数变 2。

Boost.Asio:网络 session 模式经常用 enable_shared_from_this + 捕获 shared_ptr 来保证异步 handler 里访问安全(官方教程与例子)。这是 C++ 网络编程里的教科书级用法。

The boost::asio::async_write

https://www.boost.org/doc/libs/latest/doc/html/boost_asio/tutorial/tutdaytime3.html

为什么在 boost::asio::async_write() 中一定要用 shared_from_this()

背景

boost::asio::async_write()异步写操作,意思是:

写入调用会立刻返回,真正的数据发送在后台进行。 一旦发送完成,系统会回调你指定的 handler。

问题:当 handler 被调用时,你的对象可能已经被销毁。 这就是为什么我们要用 shared_from_this() —— 它可以延长对象的生命期直到回调执行完。


✅ 正确写法(使用 shared_from_this)
代码语言:javascript
复制
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
#include <thread>

using boost::asio::ip::tcp;

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(boost::asio::io_context& io) : socket_(io) {}

    tcp::socket& socket() { return socket_; }

    void start() {
        message_ = "Hello, async world!\n";

        std::cout << "[start] begin async_write\n";

        // 捕获 shared_from_this,保证 Session 在回调完成前不被销毁
        boost::asio::async_write(socket_,
            boost::asio::buffer(message_),
            [self = shared_from_this()](boost::system::error_code ec, std::size_t bytes) {
                if (!ec) {
                    std::cout << "[callback] write complete, bytes=" << bytes << "\n";
                }
                else {
                    std::cout << "[callback] error: " << ec.message() << "\n";
                }
            });
    }

private:
    tcp::socket socket_;
    std::string message_;
};

int main() {
    boost::asio::io_context io;

    tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 12345));

    auto session = std::make_shared<Session>(io);
    acceptor.async_accept(session->socket(),
        [session](boost::system::error_code ec) {
            if (!ec) session->start();
        });

    io.run();
}

想象:

你(Session)发起了一个异步操作,就像「让别人帮你跑腿」;

如果没人帮你记着这件事(shared_ptr 引用计数为 0),你下班回家了;

跑腿回来敲门(回调触发),你人已经不在 → 崩溃。

循环引用

代码语言:javascript
复制

// EventLoop provides a main loop that notifies EventHandler 
// callback objects when I/O is ready on a file descriptor.
class EventLoop : public hf3fs::enable_shared_from_this<EventLoop> 
{

	// start and stop.

	Result<Void> start(const std::string &threadName = "EventLoop");

	class EventHandler
	{

		public:

		virtual void handleEvents(uint32_t epollEvents) = 0;


		protected:

		friend class EventLoop;

		std::weak_ptr<EventLoop> eventLoop_;
	};
背景

你有两个类:

代码语言:javascript
复制
class EventLoop;  // 事件循环

class EventHandler {
protected:
    std::weak_ptr<EventLoop> eventLoop_;  // ⭐ 弱引用
};

EventHandler 是某种事件处理器(可能是定时器、IO 回调等)。

每个 EventHandler 需要访问它所属的 EventLoop 来注册事件、触发回调。

问题:EventLoop 和 EventHandler 互相持有怎么办?

循环引用问题

假设:

代码语言:javascript
复制
class EventLoop : public std::enable_shared_from_this<EventLoop> {
    std::vector<std::shared_ptr<EventHandler>> handlers_;
};

EventLoop 持有 shared_ptr<EventHandler>(EventHandler 不可能离开事件循环前就被销毁)。

如果 EventHandler 内部又写成:

代码语言:javascript
复制
std::shared_ptr<EventLoop> eventLoop_;

就会发生循环引用

代码语言:javascript
复制
EventLoop.shared_ptr -> EventHandler.shared_ptr -> EventLoop.shared_ptr

shared_count 永远 > 0

EventLoop 和 EventHandler 永远不会被析构 → 内存泄漏

解决方案:弱引用

使用 weak_ptr

代码语言:javascript
复制
class EventHandler {
protected:
    std::weak_ptr<EventLoop> eventLoop_; // 弱引用,不增加 shared_count
};

EventHandler 可以访问 EventLoop,但不阻止它销毁。

当需要访问 EventLoop 时,用 lock()

代码语言:javascript
复制
void EventHandler::handleEvent() {
    if (auto loop = eventLoop_.lock()) {   // 尝试生成 shared_ptr
        loop->doSomething();
    } else {
        // EventLoop 已经被销毁,安全退出
    }
}

如果 EventLoop 已销毁,lock() 返回空指针,不会访问悬空对象。

关键:避免循环引用,同时保证访问安全。

小比喻理解

EventLoop = 厨师

EventHandler = 菜品

菜品知道厨师是谁(weak_ptr),但它不控制厨师生命

厨师决定什么时候下班(销毁 EventLoop)

菜品还能安全检查:“厨师还在吗?” (lock())

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端开发成长指南 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Part 1 面试官提问:
  • Part 2 我的回答
    • 2.2 std::shared_ptr
      • 场景 1:C++11新特性之十:enable_shared_from_this
      • ✅ 改进:用 shared_from_this() 延长生命周期
      • 疑问:shared_from_this是如何做到“让一个对象知道自己被shared_ptr` 管着”的
      • 一、根本问题:普通类为什么不行?
      • 二、原理:enable_shared_from_this 是怎么“绑定”上的?
      • 三、调用时的过程
    • 要点先说清楚
      • 举个完整例子(含控制流程)
      • 循环引用
      • 解决方案:弱引用
      • 小比喻理解
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档