如何让绑定对象专属删除器的std::unique_ptr禁止非nullptr参数的reset调用
方案1:编译期禁止非nullptr的reset调用(推荐)
核心思路是利用std::unique_ptr的特性:reset方法接受的参数类型和删除器定义的pointer别名完全兼容。我们只需要自定义一个仅允许从nullptr构造的指针包装类型作为删除器的pointer,就能在编译期直接拦截所有传入裸指针的reset调用。
修改后的完整实现代码如下:
// 自定义指针包装类型,仅允许nullptr构造,内部可访问裸指针 template<typename T> struct SafePtr { T* raw_ptr = nullptr; // 仅允许nullptr隐式转换,外部传裸指针会编译失败 constexpr SafePtr(std::nullptr_t) noexcept {} private: // 内部构造接口,仅开放给删除器和工厂函数 explicit SafePtr(T* p) noexcept : raw_ptr(p) {} friend struct MyDeleter<T>; template<typename U, typename... Args> friend my_unique_ptr<U> make_my_unique(Args&&... args); }; template<typename T> struct MyDeleter { // 定义删除器的pointer类型为自定义包装类 using pointer = SafePtr<T>; size_t objectSize; MyDeleter() : objectSize(sizeof(T)) {} template<typename S> MyDeleter(const MyDeleter<S>& other) noexcept : objectSize(other.objectSize) {} void operator()(pointer p) { T* t = p.raw_ptr; if (!t) return; t->~T(); coolAllocatorLibrary::deallocate(t, objectSize); } }; template<typename T> using my_unique_ptr = std::unique_ptr<T, MyDeleter<T>>; template<typename T, typename... Args> my_unique_ptr<T> make_my_unique(Args&&... args) { T* ptr = static_cast<T*>(coolAllocatorLibrary::allocate(sizeof(T))); try { new (ptr) T(std::forward<Args>(args)...); } catch(...) { coolAllocatorLibrary::deallocate(ptr, sizeof(T)); throw; } // 用私有构造的SafePtr构造unique_ptr return my_unique_ptr<T>(SafePtr<T>(ptr)); }
此时用户调用ptr.reset(new Base())时,因为Base*无法隐式转换为SafePtr<Base>,会直接编译报错,只有ptr.reset(nullptr)可以正常执行,完全符合需求。这个方案仅修改删除器和工厂函数代码,无需封装整个std::unique_ptr,实现成本极低。
方案2:运行期校验(兼容老代码场景)
如果因为历史代码限制无法修改删除器的pointer类型,可以在删除器的调用逻辑中增加运行时校验,配合调试期的断言快速发现误用:
template<typename T> struct MyDeleter { size_t objectSize; // 存储创建时的类型信息用于校验 const std::type_info* typeInfo = &typeid(T); MyDeleter() : objectSize(sizeof(T)) {} template<typename S> MyDeleter(const MyDeleter<S>& other) noexcept : objectSize(other.objectSize), typeInfo(other.typeInfo) {} void operator()(T* t) { if (!t) return; // 调试期校验类型匹配,若reset传入了不同类型的指针会触发断言 assert(typeid(*t) == *typeInfo && "reset传入的对象类型与删除器不匹配"); t->~T(); coolAllocatorLibrary::deallocate(t, objectSize); } };
这个方案无法在编译期拦截错误,但可以在调试阶段第一时间发现误用,避免线上问题。
你提到的类PMR分配器场景的std::unique_ptr原生支持,目前C++标准委员会已有相关提案在讨论,在标准落地前,上述自定义删除器改造方案已经可以覆盖绝大多数场景的需求。
内容的提问来源于stack exchange,提问作者gexicide
相关产品推荐
相关产品推荐

