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

使用带自定义删除器的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++中类前置声明允许不完整类型用于指针/引用、类成员函数实现可推迟到类定义之后的特性,打破循环:

  1. 前置声明Node
  2. 先定义依赖不完整Node的CustomDeleter和Alloc类框架
  3. 完整定义Node
  4. 最后实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 17:57:24