C++中的慢速递归析构函数调用

Slow recursive destructor call in C++

本文关键字:递归 析构 函数调用 C++      更新时间:2023-10-16

我使用C++来表示使用引用计数对象的大型图。类似于:

// Public class
class Pointer{
  public:
  // Constructors
  Pointer(...){ node = ...; node->counter++;}
  // Copy constructor
  Pointer(Pointer& const other){ node = other.node; node->counter++;}
  // Destructor
  ~Point(){ if(--node->counter==0) delete node;}
  // Other methods
  ...
  Node* node;
};
// Node base class
class Node{
  // Constructor
  Node():counter(0){}
  // Destructor
  virtual ~Node(){}
  // public:
  unsigned long counter;
};
// Node derived class
class NodeDerived : public Node{
  // Constructors and other methods
  ...
  // Destructor
  virtual ~NodeDerived(){ ... }
  // Children
  Pointer children[2];
};

因此存在一个公共类Pointer,它包含一个指向多态类Node的指针。从Node派生的某些类将包含新的Pointer实例。注意,几个Pointer实例可以指向同一个Node实例。

我使用这个类来构建具有数百万个实例的非常大的图,这很好。当要删除图形时,问题就出现了。对于默认的析构函数实现,这会导致堆栈溢出,因为在根节点的析构因子中,调用子节点的析构函数,在子节点的析构函数中,调用孩子节点的子节点的解构函数,依此类推

我通过为上面的NodeDerived类实现一个析构函数来解决这个问题,该析构函数通过使用堆栈来避免递归调用。这是可行的,但仍然非常缓慢,我正在寻找一种加快速度的方法。是否可以在不导致内存泄漏的情况下避免声明析构函数virtual

如果在销毁过程中处理数百万个对象和堆栈溢出,那么您可能需要研究的是将指向所有节点对象的指针存储在某种类型的容器中,该容器不允许重复值并支持迭代,如std::setstd::unordered_set。然后在图节点本身中,只存储指向连接的图节点的指针。在删除图时,不要为了删除节点而遍历每个图节点的指针。。。相反,只需转到包含指向整个图中节点集的指针的容器,然后遍历容器,逐个销毁每个节点。不需要递归,因为你不需要"跟随"每个图节点的指针集来破坏图节点连接的所有节点。你也不需要担心std::shared_ptr的空间和时间开销。

如果不单独删除节点,您可能需要考虑使用内存池而不是引用计数。内存池可以让你非常快地毁掉整个图形,代价是(取决于池的类型)在整个图形被破坏之前,可能无法恢复单个被删除对象使用的内存。

是否可以在不导致内存泄漏的情况下以某种方式避免声明析构函数为虚拟的?

恐怕不是,更确切地说,在指针的动态类型和静态类型不匹配的指针上调用delete p时,您无法避免未定义的行为。

但是仍然很慢

恐怕就是这样。删除数百万个对象需要一些时间。你可以绕过它,例如避免完全破坏单个物体,例如将它们全部放在某个水池中(并将其作为一个整体丢弃)。取决于你的破坏需求。