应使用 std::unique_ptr 或 std::shared_ptr 封装任务对象,配合线程安全队列与明确所有权流转;裸指针因无所有权语义,易致 double-free、悬空指针及 raii 失效,在高并发 web 服务器中危险且低效。

直接用裸指针管理任务分发,在高并发 Web 服务器里是危险且低效的。你应该用 std::unique_ptr 或 std::shared_ptr 封装任务对象,并配合线程安全队列与明确的所有权流转,才能兼顾性能、安全与可维护性。
为什么不能用 raw pointer 做任务分发
裸指针(Foo*)不携带所有权语义,导致三个典型问题:
- 谁 delete?多个线程可能同时尝试释放同一块内存,引发 double-free 或 use-after-free
- 生命周期难追踪:任务入队后,原始作用域结束,但指针仍被工作线程持有,极易悬空
- 无法与 RAII 机制协同:无法自动绑定资源(如 socket 句柄、buffer 内存)的释放时机
尤其在 Boost.Asio 或自研 reactor 模型中,任务常以 lambda 或 functor 形式提交到 io_context::post(),此时若捕获裸指针,风险指数级上升。
std::unique_ptr 是任务分发的默认选择
绝大多数任务对象(如 HttpRequest、TaskContext)只应有一个所有者——即负责调度的主线程或 acceptor 线程。用 std::unique_ptr 明确表达“移交所有权”语义,既零开销又防误用:
- 构造时用
std::make_unique<t>(...)</t>,避免new+unique_ptr两步异常不安全 - 入队操作必须 move:调用
queue.push(std::move(task_ptr)),禁止拷贝 - 工作线程 pop 后直接接管,析构自动触发,无需手动
delete - 若需跨线程传递取消信号,可额外持有一个
std::atomic<bool>&</bool>引用,而非共享指针
示例片段:
struct HttpRequest {
std::string method;
std::string path;
std::vector<char> body;
};
// 安全的任务创建与分发
auto req = std::make_unique<httprequest>();
req->method = "GET";
req->path = "/api/status";
task_queue.push(std::move(req)); // 所有权移交,原变量变为空
</httprequest></char>
什么情况下才该用 std::shared_ptr
仅当任务对象需被多个实体同时持有且生命周期不确定时,才考虑 shared_ptr。典型场景包括:
- WebSocket 连接句柄需被消息处理器、心跳定时器、断连清理器三方引用
- HTTP 响应体 buffer 被 encoder 和 send completion handler 共享
- 任务状态对象需被前端监控接口和后台调度器同时读取
但要注意:shared_ptr 的引用计数原子操作有微小开销,在每秒数万任务的场景下,应避免无谓的 shared_from_this() 或频繁拷贝。更优解常是拆分数据:只共享不可变元数据(用 shared_ptr<const t></const>),而可变状态仍由唯一所有者控制。
容易被忽略的边界:异步回调中的指针生命周期
这是最常踩坑的地方。例如在 Beast 中写:
http::async_read(socket, buffer, [this, req_ptr](error_code ec, std::size_t) {
handle_request(std::move(req_ptr)); // ❌ 错误:lambda 捕获的是值拷贝,但 req_ptr 是 unique_ptr,移动后原 lambda 内部已空
});
正确做法是捕获 by reference(确保外部对象存活足够久)或显式 move 到 lambda 参数中(C++20 后支持)。更稳妥的方式是把任务包装进一个带状态的 functor 类,用 shared_ptr 包裹它,让回调逻辑与数据共生死——但这会引入引用计数开销,务必测量确认影响可接受。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











