C ++中的类析构函数内存处理

时间:2010-05-14 08:39:13

标签: c++ memory-leaks destructor

隐式析构函数处理的潜在内存泄漏是什么?我知道如果你有任何存储在堆上的东西它将无法处理它,如果你有一个文件或数据库的连接,那需要手动处理。还有别的事吗?那么,像矢量这样的非基础数据类型呢?

另外,在一个显式的析构函数中,需要销毁那些本已被隐式破坏的非堆变量,还是会自动处理它们?

由于

7 个答案:

答案 0 :(得分:4)

向量等会释放自己,因为它们的析构函数被调用。

事实上,你的问题将出现在任何导致悬空指针(或句柄或其他)问题的地方,即任何需要手动关闭或取消分配的东西都不会在隐式析构函数中。任何破坏自己的东西都会好的。这就是为什么RAII成语如此受欢迎:)

答案 1 :(得分:4)

我认为这个问题是颠倒的。不要考虑所做的对象破坏:根据做的事情来思考。

如果一个类只有一个隐式析构函数,那么当它被销毁时,所有非静态数据成员也会被销毁,就像所有基类子对象一样。

你可以争论一下这是否是由“隐式析构函数”完成的 - 如果你写了一个析构函数,那些事情仍然会发生。它是对象销毁过程本身的一部分,而不是析构函数的代码的一部分。

因此,如果您的“与文件的连接”是FILE *类成员,那么默认的析构函数不会释放它,因为FILE *是C的东西,并且它没有析构函数。销毁文件*什么都不做。如果“与文件的连接”是std::ofstream,那么它确实有一个析构函数,析构函数会尝试刷新并关闭连接。

Vector有一个析构函数来释放向量的资源。这意味着反过来,向量中的元素的析构函数称为。

对于您拥有的每个成员变量,您应该查看相应的文档以了解如何为该类型处理资源。显然,有了经验,你就会开始记住答案。

如果成员变量是指针,则隐式析构函数不会对其执行任何操作。因此,如果它指向堆分配的内存,并且该对象拥有指向该内存的唯一指针,那么您需要释放它以避免内存泄漏。

如果你发现自己在C ++中编写了一个需要释放多个东西的析构函数,那么你可能已经很糟糕地设计了这个类。在课堂上的其他地方几乎肯定存在异常安全错误:它可以使代码正确,但如果你有足够的经验来做到正确,那么你就有足够的经验通过不同的方式设计自己的生活; )。解决方案是编写具有单一职责的类 - 管理单个资源 - 并将它们用作成员变量。或者,更好的是,找到管理您正在使用的资源的库类。 shared_ptr非常灵活。

“需要你销毁非堆变量”

在C ++中没有“堆”和“非堆”变量这样的东西。销毁指针不会释放指向的东西,原因很明显,指针类型中没有任何东西告诉你它指向的东西是如何分配的,或者是谁“拥有”它。

答案 2 :(得分:3)

你需要了解有关析构函数的三件事。假设您有一个具有三个成员的t类对象T

class T
{
    A a;
    B b;
    C c;

    // ...

} t;
  1. 销毁总是意味着按顺序调用以下析构函数:~T()~C()~B()~A()您无法影响这些语义。无法防止成员被破坏。无论您是手动定义~T()还是编译器生成它,都会发生这种情况。

  2. 隐式生成的析构函数总是无操作。但是,如第1点所述,之后仍会执行析构函数~C()~B()~A()

  3. 标量类型的析构函数,尤其是C风格的指针,总是一个无操作。当你有一个C风格的指针成员时,这几乎总是需要手动编写析构函数(以及复制构造函数和赋值运算符)的唯一原因 - 在~T()完成后,破坏C风格指针成员没有任何意义,因此指针上的任何清理都必须在~T()中完成。

  4. 我希望这能为你解决问题。

答案 3 :(得分:2)

  

隐式析构函数处理的潜在内存泄漏是什么?我知道如果你有任何存储在堆上的东西它将无法处理它,如果你有一个文件或数据库的连接,那需要手动处理。还有别的事吗?那么,像矢量这样的非基础数据类型呢?

简单地说,你是对的。隐式析构函数唯一没有处理的是以指针形式分配的内存或需要显式释放的其他类型的资源。

关于向量或任何其他类类型对象;所有类都有一个析构函数,负责释放他们的数据。当它超出范围时,会调用此类对象的析构函数。所有基本数据类型如:int, float, double, short, bool等都以类似的方式发布。

  

另外,在一个显式的析构函数中,需要销毁那些本已被隐式破坏的非堆变量,还是会自动处理它们?

答案是它们是自动处理的,实际上你永远不应该尝试显式调用对象的析构函数。

在隐式析构函数中,会发生以下情况:

  • 该类的每个成员变量都依次调用它们的析构函数。

在显式析构函数中,会发生以下情况:

  • 执行显式析构函数的主体
  • 该类的每个成员变量都依次调用它们的析构函数。

因此,您可以看到显式析构函数与隐式析构函数非常相似,除了您可以进行任何必要的手动干预。

现在作为管理内存分配对象的一些建议,您几乎应该总是使用RAII(资源获取是初始化)。这个问题的关键是智能指针,这些指针在超出范围时被正确删除,就像非堆分配对象一样。一旦你开始使用它们,所有权就成了一个问题,但那是另一天的故事。开始使用智能指针的好地方是boost::shared_ptr。 (顺便说一句,如果你还没有加速,你写c ++代码帮自己一个忙...)

答案 4 :(得分:1)

隐式析构函数在所有成员变量上调用析构函数。

如果你有一个原始指针,它的析构函数什么都不做。因此,如果您拥有它指向的内存,则必须明确释放它。

这同样适用于任何其他资源,或者您希望采取的任何其他操作,而不是成员变量的默认析构函数所暗示的。

仍然会调用成员的默认析构函数,因此vector,auto_ptrs,使用std流或其他C ++库打开的文件都会被破坏。道德是包装任何需要在C ++类中释放的OS对象,这些对象可以自我整理,因此应用程序类不必担心它。

答案 5 :(得分:0)

类的析构函数将隐式调用所有非静态成员的析构函数,然后是虚拟基类的析构函数,然后是自己的代码。

如果你有STL容器作为类成员,你不必明确地做任何事情 - 他们将在他们的析构函数中释放自己的记忆。

答案 6 :(得分:0)

你已经做出了错误的假设。如果我的类使用std::auto_ptr<Data>保存堆分配的数据,则隐式dtor 处理它。没有记忆会被泄露。原因在于,与任何其他dtor一样,隐含的dtor将会调用所有成员和基类的dtors。

现在,一个好的班级设计将所有重要的资源都作为具有适当dtors的成员。因此,dtor机构不需要做任何事情来防止资源泄漏。当然,该规则有一个例外:资源管理类本身只管理一个资源,因此清理它们中的一个资源。