使用带自定义删除器的unique_ptr实现树结构的循环依赖问题
解决树状结构中Node与定长分配器的循环依赖问题
问题背景
实现固定大小Node的树状结构时,使用自定义定长分配器Alloc管理内存,但出现循环依赖:
Alloc需要知晓Node类型以获取sizeof(Node)和alignof(Node)Node中的std::unique_ptr<Node, CustomDeleter>依赖CustomDeleter,而CustomDeleter又依赖Alloc
代码示例:
struct Node { // 其他属性... std::array<std::unique_ptr<Node, CustomDeleter>, num_children> children_; };
注:无法使用std::allocator<Node>,需支持默认内存资源之外的自定义内存资源。
可行解决方案
方法1:前置声明+推迟分配器实现
利用C++中类前置声明允许不完整类型用于指针/引用、类成员函数实现可推迟到类定义之后的特性,打破循环:
- 前置声明
Node - 先定义依赖不完整
Node的CustomDeleter和Alloc类框架 - 完整定义
Node - 最后实现
Alloc中需要Node完整类型的成员函数(分配/释放逻辑)
代码示例:
// 前置声明Node struct Node; // 自定义删除器,持有分配器指针(需保证分配器生命周期长于Node) struct CustomDeleter { Alloc* alloc_; void operator()(Node* ptr) const { alloc_->deallocate(ptr); } }; // 先定义分配器类框架,暂不实现需要Node完整类型的方法 class Alloc { public: Node* allocate(); void deallocate(Node* ptr); // 持有自定义内存资源句柄 void* memory_resource_; }; // 完整定义Node struct Node { // 其他属性... std::array<std::unique_ptr<Node, CustomDeleter>, num_children> children_; }; // 此时Node已完整,可实现Alloc的成员函数 Node* Alloc::allocate() { void* mem = memory_resource_->allocate(sizeof(Node), alignof(Node)); return new(mem) Node(); // 原地构造Node } void Alloc::deallocate(Node* ptr) { ptr->~Node(); // 手动调用析构函数 memory_resource_->deallocate(ptr, sizeof(Node), alignof(Node)); }
方法2:将分配器类型作为Node的模板参数
把Node设计为模板类,将Alloc和对应的CustomDeleter作为模板参数传入,彻底消除循环依赖:
// 先定义通用的分配器和删除器模板 template <typename T> struct CustomDeleter { Alloc<T>* alloc_; void operator()(T* ptr) const { alloc_->deallocate(ptr); } }; template <typename T> class Alloc { public: T* allocate() { void* mem = memory_resource_->allocate(sizeof(T), alignof(T)); return new(mem) T(); } void deallocate(T* ptr) { ptr->~T(); memory_resource_->deallocate(ptr, sizeof(T), alignof(T)); } void* memory_resource_; }; // 模板化Node,接收分配器和删除器类型 template <typename AllocType, typename DeleterType> struct Node { // 其他属性... std::array<std::unique_ptr<Node, DeleterType>, num_children> children_; }; // 使用时通过别名简化递归实例化 template <typename T> using NodeAlloc = Alloc<T>; template <typename T> using NodeDeleter = CustomDeleter<T>; using MyNode = Node<NodeAlloc<MyNode>, NodeDeleter<MyNode>>;
方法3:让删除器通过Node获取分配器
修改Node结构,让每个Node持有分配器指针,删除器无需直接依赖Alloc,而是从Node中获取分配器:
// 前置声明Node struct Node; struct CustomDeleter { void operator()(Node* ptr) const { // 从Node实例中获取分配器并执行释放 ptr->alloc_->deallocate(ptr); } }; class Alloc { public: Node* allocate() { void* mem = memory_resource_->allocate(sizeof(Node), alignof(Node)); Node* node = new(mem) Node(); node->alloc_ = this; // 将分配器绑定到Node return node; } void deallocate(Node* ptr) { ptr->~Node(); memory_resource_->deallocate(ptr, sizeof(Node), alignof(Node)); } void* memory_resource_; }; struct Node { Alloc* alloc_; // 持有分配器指针 // 其他属性... std::array<std::unique_ptr<Node, CustomDeleter>, num_children> children_; };
注意事项
- 使用分配器指针时,必须保证分配器的生命周期长于所有
Node实例,避免悬空指针 - 若需更安全的生命周期管理,可改用
std::shared_ptr<Alloc>替代裸指针 - 原地构造/释放时,需手动调用
new(mem) Node()(构造)和ptr->~Node()(析构)
内容的提问来源于stack exchange,提问作者frozenca
相关产品推荐
相关产品推荐

