You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何带自定义删除器的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:39:11