【问题标题】:Recursive Destructors in C++C++ 中的递归析构函数
【发布时间】:2012-02-03 16:55:48
【问题描述】:

假设我有一个类似于以下的类:

#include <vector>

class element
{
public:
   element();
   ~element();

   virtual void my_func();

private:
   std::vector<element*> _elements;
};

我将如何实现析构函数?

我在想这样的事情,但我不确定。我担心内存泄漏,因为我对 C++ 比较陌生。

void element::destroy()
{
   for(int i = 0; i < _elements.size(); ++i)
      _elements.at(i)->destroy();

   if(this != NULL) 
      delete this;
}

element::~element()
{
    destroy();
}

谢谢。

PS: 这是一个示例主要:

int main()
{
   element* el_1 = new element();
   element* el_2 = new element();
   element* el_3 = new element();

   el_1.add_element(el_2);
   el_2.add_element(el_3);

   return 0;
}

另外,如果我这样做(也就是不要使用new)怎么办:

int main()
{
   element el_1;
   element el_2;
   element el_3;

   el_1.add_element(&el_2);
   el_2.add_element(&el_3);

   return 0;
}

【问题讨论】:

  • 您可以调用delete _elements.at(i),并且您不应该在析构函数中测试this 不为空。不能为空。
  • 会调用delete _elements.at(i) 正确处理所有子元素吗?换句话说,它们会以正确的顺序(自下而上)处理吗?因为我认为如果我删除了一个element,它的_elements 中仍然有东西,那将是一个泄漏。
  • 当你delete something;something的类的析构函数被调用,然后指针被释放。
  • 您应该尽可能避免使用原始指针。在这种情况下:确保您确实需要一个指针向量(通常仅在 element 是抽象基类或不可复制时才需要),如果是这样,则首选 ptr_vector
  • @Philipp:在这种情况下,需要一个指针容器:标准库容器不能用不完整的类型实例化,因此element 不能有std::vector&lt;element&gt; 作为成员。 (或者,可以使用第三方容器库。Boost 的容器库对实例化的要求不那么严格。)

标签: c++ memory-leaks destructor


【解决方案1】:
element::~element()
{
    typedef std::vector<element*>::const_iterator iterator;
    for (iterator it(_elements.begin()); it != _elements.end(); ++it)
        delete *it;
}

delete this 在析构函数中总是错误的:this 已经被销毁了!

此外,您还需要声明一个复制构造函数和复制赋值运算符(或者让它们未定义,使您的类不可复制,或者提供一个合适的定义来复制树)。

或者(并且最好),您应该为_elements 使用智能指针容器。例如,

std::vector<std::unique_ptr<element>> _elements;

element 被销毁时,它的_elements 容器将被自动销毁。当容器被销毁时,它会销毁它的每一个元素。一个std::unique_ptr拥有它所指向的对象,当std::unique_ptr被销毁时,它会销毁它所指向的元素。

通过在此处使用std::vector&lt;std::unique_ptr&lt;element&gt;&gt;,您无需提供自己的析构函数,因为所有这些内置功能都会为您完成清理工作。

如果您希望能够复制 element 树,您仍然需要提供自己的复制构造函数和复制赋值运算符来克隆树。但是,如果您不需要树是可复制的,则不需要像自己管理内存那样声明复制操作:std::unique_ptr 容器本身不可复制,因此它作为成员存在变量将抑制隐式生成的复制操作。

【讨论】:

  • 那么,在这种情况下,您能否将delete *it 视为对~element 的递归调用?
  • @Eric:实际上,删除一个对象会首先调用对象的析构函数(它应该清理它负责的任何东西),然后释放对象占用的内存。
【解决方案2】:
class element
{
public:
   element();
   ~element();

private:
   std::vector<element*> _elements;
};

根据Rule of Three,这缺少复制构造函数赋值运算符的定义。


element::~element()
{
    destroy();
}

从析构函数调用delete this(这是destroy() 所做的)总是错误,因为析构函数是在当前对象被调用时调用的删除。


for(int i = 0; i < _elements.size(); ++i)
   _elements.at(i)->destroy();

虽然这可以对您调用destroy() 的对象调用delete,但这种安排的缺点是您不得通过调用destroy() 以外的方式销毁此类对象。特别是这个

element el_1;

会是个问题,因为e1_1 会在它掉出作用域bay 调用它的析构函数时被自动销毁。由于这会调用destroy(),它会在未使用new 分配的对象上调用delete this,这会导致未定义行为。 (如果你幸运的话,它会立刻炸到你的脸上。)

最好从析构函数中删除delete this,并且只让析构函数删除对象向量中的对象:

for(std::vector<element*>::size_type i = 0; i < _elements.size(); ++i)
  delete _elements[i];

if(this != NULL) 
   delete this;

检查NULLness 的指针从来没有必要,因为delete 被定义为当传递给它的指针是NULL 时什么都不做。

【讨论】:

  • 最后一段有误导性; if 从来都不是必需的,而不是整个 sn-p(尽管作为相关说明,当然 delete this 很少有用!)
  • @Lightness:我正在改进这一点。 :)
【解决方案3】:
  1. delete this 不应该存在,因为根据定义,该对象已经处于破坏状态。

  2. 如果复制element,则其成员vector 中的所有指针也会被复制;然后当原始文件超出范围时,它们的指针被销毁,element 副本有一个vector 的悬空指针。您需要一个复制 ctor 和赋值运算符。

所以,仅仅这个基本的开始就已经造成了两个非常严重的错误。这是要避免设计的标志。对于使用它的程序员来说,所有权语义是什么似乎并不那么清楚。

为什么这里需要动态分配?为什么不自己存储elements 对象?

#include <vector>

class element
{
public:
   void add_element(element const& e) {
      _elements.push_back(e);
   }
private:
   std::vector<element> _elements;
};

如果您之前有多个对同一个 element 的引用进入不同的其他 elements,这将巧妙地改变您的程序,但您必须自己决定是否需要这种纠结的依赖关系。

【讨论】:

  • 感谢 cmets -- 我需要使用指针的原因是因为 element 旨在成为具有 virtual 函数的基类。这是我能看到的确保执行正确形式的虚函数的唯一方法。
  • @Eric:好的;那可能是有道理的。不过,请考虑像 std::shared_ptr 这样的包装指针实现。
【解决方案4】:

不,这样不行。你永远不会删除this。你只是不这样做。基本上,取出你的销毁方法,将所有内容放入~element(),除了delete this 部分。而不是调用destroy方法,只需delete _elements[i];

【讨论】:

  • delete this; 有效且偶尔有用。
  • 是的,但你仍然从不这样做,就像你从不做某些其他事情一样,有时你最终还是会这样做,因为它很方便。
猜你喜欢
  • 2016-03-13
  • 1970-01-01
  • 2017-04-09
  • 2015-07-01
  • 2017-06-12
  • 1970-01-01
  • 2011-03-05
  • 2021-07-31
  • 1970-01-01
相关资源
最近更新 更多