C++中如何让不同删除器的std::unique_ptr兼容并存入指定数组
问题1 实现删除器兼容的方案
std::unique_ptr的删除器是其类型的组成部分,两种携带不同删除器类型的unique_ptr默认属于完全独立的类型,无法直接互相赋值。要实现需求有两种可行方案:
方案1:给目标删除器增加转换适配
首先修正删除器的定义(你当前的operator()缺少指针入参,不符合unique_ptr对删除器的要求),给deleter<ALLOC>增加empty_deleter的转换构造,同时增加空逻辑标识:
template<typename ALLOC> class deleter { public: // 支持从empty_deleter隐式转换 template<typename T> deleter(const empty_deleter<T>&) noexcept : m_is_empty(true) {} // 原有默认构造,用于普通删除场景 explicit deleter(ALLOC alloc = ALLOC()) noexcept : m_alloc(std::move(alloc)), m_is_empty(false) {} void operator()(T* ptr) const { if (m_is_empty) return; // 原有删除逻辑:先析构对象再释放内存 std::allocator_traits<ALLOC>::destroy(m_alloc, ptr); m_alloc.deallocate(ptr, 1); } private: ALLOC m_alloc; bool m_is_empty; };
赋值时需要显式转移指针所有权,构造目标类型的unique_ptr:
std::unique_ptr<T, empty_deleter<T>> empty_ptr = /* 待存入的指针对象 */; // 构造目标类型后存入数组 storage[i] = std::unique_ptr<T, deleter<std::allocator<T>>>( empty_ptr.release(), empty_deleter<T>{} );
方案2:用类型擦除统一删除器类型
将存储数组的删除器统一替换为类型擦除后的std::function<void(T*)>,不需要修改原有删除器的定义就可以兼容所有符合签名的删除器:
// 修改数组的元素定义 std::array<std::unique_ptr<T, std::function<void(T*)>>, 数组长度> storage; // 存入带empty_deleter的指针 std::unique_ptr<T, empty_deleter<T>> empty_ptr = /* 待存入对象 */; storage[i] = std::unique_ptr<T, std::function<void(T*)>>( empty_ptr.release(), empty_deleter<T>{} ); // 普通带deleter的指针也可以直接存入 std::unique_ptr<T, deleter<std::allocator<T>>> normal_ptr = /* 普通指针对象 */; storage[i] = std::unique_ptr<T, std::function<void(T*)>>( normal_ptr.release(), deleter<std::allocator<T>>{} );
问题2 删除逻辑的调用规则
赋值完成后触发删除时,调用的是目标unique_ptr声明的删除器类型,也就是deleter<allocator<T>>的operator()。
原因是std::unique_ptr的删除器采用值语义存储:当你把携带empty_deleter的unique_ptr转换为目标类型时,会先以empty_deleter为入参构造一个deleter<allocator<T>>的实例,这个新的删除器实例会被存入目标unique_ptr,删除时只会调用该实例的operator(),原empty_deleter实例不会被保留。
如果你采用方案1的适配逻辑,目标删除器会根据m_is_empty标识走空分支,符合预期;如果没有做适配直接强制转换,会直接执行deleter<allocator<T>>原有的析构+释放逻辑,引发未定义行为。
内容的提问来源于stack exchange,提问作者Martin Kopecký

