基于cnats库的RAII包装器异步发布时的智能指针所有权问题
解决cnats异步发布的双重释放问题:基于unique_ptr的所有权转移方案
核心思路
放弃std::shared_ptr,改用std::unique_ptr管理natsMsg——因为natsMsg的所有权是独占性的,shared_ptr的引用计数反而会增加竞态风险。关键是利用unique_ptr::release()方法手动解除智能指针对对象的管理,让C异步库接管所有权并负责销毁。
具体实现步骤
- 定义带自定义删除器的unique_ptr类型
#include <memory> #include <nats.h> // 定义独占式智能指针,默认删除器为natsMsg_Destroy using NatsMsgPtr = std::unique_ptr<natsMsg, decltype(&natsMsg_Destroy)>;
- 创建natsMsg对象
初始化智能指针时绑定删除器:
// 示例:创建消息(实际参数根据业务调整) natsMsg* rawMsg = nullptr; natsStatus status = natsMsg_Create(&rawMsg, "subject", nullptr, "payload", strlen("payload")); if (status != NATS_OK) { // 处理创建失败逻辑 return; } NatsMsgPtr msg(rawMsg, natsMsg_Destroy);
- 同步发布(无需转移所有权)
js_PublishMsg不接管消息所有权,直接传递裸指针即可,智能指针会在作用域结束时自动销毁消息:
status = js_PublishMsg(js, msg.get(), nullptr); if (status != NATS_OK) { // 处理发布失败逻辑 } // 作用域结束后,msg自动调用natsMsg_Destroy
- 异步发布(转移所有权给C库)
调用js_PublishMsgAsync前,用release()释放智能指针的所有权——此时智能指针不再持有对象,不会触发删除器,由C库接管后自行销毁:
natsMsg* transferredMsg = msg.release(); status = js_PublishMsgAsync(js, transferredMsg, publishCallback, nullptr); if (status != NATS_OK) { // 发布失败,C库不会接管消息,手动销毁避免内存泄漏 natsMsg_Destroy(transferredMsg); } // 此时msg为空,后续不可再使用
关键注意事项
- 绝对避免使用
shared_ptr:shared_ptr的多副本引用计数会导致即使调用release,其他副本仍可能触发删除器,引发双重释放。 - 异步发布失败必须手动回收:如果
js_PublishMsgAsync返回错误,C库不会接管消息,必须手动调用natsMsg_Destroy释放裸指针。 - 转移所有权后不要复用原智能指针:
release()后unique_ptr变为空,再次访问会触发未定义行为。
内容的提问来源于stack exchange,提问作者John Sallay
相关产品推荐
相关产品推荐

