为何带自定义删除器的unique_ptr遇nullptr失效,shared_ptr却正常?
嘿,这个问题我之前在项目里也踩过坑,咱们来把它掰扯清楚:
核心原因:C++标准对unique_ptr和shared_ptr的析构逻辑规定不同
首先明确一点:这不是VC2017的特性,而是C标准的明确规定:
- 对于
unique_ptr<T, Deleter>,当它持有nullptr时,析构函数不会调用自定义删除器。标准里的逻辑很直白:只有当内部存储的指针不为空时,才会执行deleter(ptr)。 - 而
shared_ptr<T>则完全不同:它的自定义删除器是和控制块绑定的,哪怕内部指针是nullptr,只要shared_ptr本身是有效的(也就是控制块存在),析构时就会调用删除器。这就是为什么你用shared_ptr能正常工作的原因。
为什么用(void*)1构造unique_ptr就可以?
因为这时候unique_ptr持有的指针不再是nullptr了,析构时会触发删除器——不过这里要提个醒:你的自定义删除器绝对不能去解引用这个(void*)1指针,不然就是未定义行为!好在你的场景是把清理逻辑完全封装在删除器里,不需要用到指针本身,所以这个临时 workaround 是可行的,但确实有点hack感。
更优雅的替代方案:自定义作用域守卫
其实用智能指针做作用域守卫本来就有点“歪门邪道”,不如直接写一个极简的作用域守卫类,逻辑更清晰,也不会有智能指针的这些规则限制:
#include <functional> class ScopeGuard { public: explicit ScopeGuard(std::function<void()> cleanup) : cleanup_func_(std::move(cleanup)) {} // 析构时执行清理逻辑,除非主动取消 ~ScopeGuard() { if (!dismissed_) { cleanup_func_(); } } // 允许手动取消清理(比如操作成功后不需要清理了) void dismiss() { dismissed_ = true; } // 禁止拷贝,允许移动 ScopeGuard(const ScopeGuard&) = delete; ScopeGuard& operator=(const ScopeGuard&) = delete; ScopeGuard(ScopeGuard&&) = default; ScopeGuard& operator=(ScopeGuard&&) = default; private: std::function<void()> cleanup_func_; bool dismissed_ = false; };
用的时候也很直观:
{ ScopeGuard guard([](){ /* 这里写你的清理逻辑,比如关闭句柄、释放资源等 */ }); // 执行你的业务逻辑 // 如果中途操作成功不需要清理了,就调用 guard.dismiss(); } // 离开作用域自动执行清理
这样是不是比折腾智能指针舒服多了?😉
内容的提问来源于stack exchange,提问作者Severin Pappadeux
相关产品推荐
相关产品推荐

