我什么时候应该为我的班级提供一个析构函数?

时间:2014-10-07 23:31:46

标签: c++ pointers memory-management destructor

这似乎是一个相当微不足道或至少是常见的问题,但我无法在Google或SO上找到令人满意的答案。

我不确定何时应该为我的班级实现析构函数。

一个明显的例子是当类包装到文件的连接时,我想确保连接已关闭,所以我在析构函数中关闭它。

但是我想知道一般,我怎么知道我是否应该定义一个析构函数。我可以检查哪些指导原则,以确定我是否应该在此课程中使用析构函数?

我能想到的一个指导原则是,该类是否包含任何成员指针。默认的析构函数会在删除时破坏指针,但不会指向它们指向的对象。所以这应该是用户定义的析构函数的工作。例如:(我是C ++新手,所以这段代码可能无法编译)。

class MyContainer {
public:
    MyContainer(int size) : data(new int[size]) { }
    ~MyContainer(){
        delete [] data;
    }
    // .. stuff omitted
private:
    int* data;
}

如果我没有提供析构函数,则销毁MyContainer对象意味着创建泄漏,因为data之前引用的所有数据都不会被删除。

但我有两个问题:

1-这是唯一的'指南'?即如果类有成员指针或者它是否正在管理资源,那么定义一个析构函数?或者还有别的吗?

2-是否有 delete成员指针的情况?参考怎么样?

3 个答案:

答案 0 :(得分:13)

如果默认销毁不够,则需要定义析构函数。当然,这只是提出了一个问题:默认的析构函数是做什么的?好吧,它调用每个成员变量的析构函数,就是这样。如果这对你来说已经足够了,那么你很高兴。如果不是,那么你需要编写一个析构函数。

最常见的例子是使用new分配指针的情况。指针(对任何类型)都是一个基元,析构函数只是让指针本身消失,而不会触及指向内存的指针。所以指针的默认析构函数对我们没有正确的行为(它会泄漏内存),因此我们需要在析构函数中进行删除调用。想象一下,现在我们将原始指针更改为智能指针。当智能指针被销毁时,它还会调用其指向的析构函数,然后释放内存。所以智能指针的析构函数就足够了。

通过了解最常见案例背后的根本原因,您可以推断出不太常见的案例。通常,如果你使用智能指针和std库容器,它们的析构函数会做正确的事情而你根本不需要编写析构函数。但仍然有例外。

假设您有一个Logger类。这个记录器类很聪明,它将一堆消息缓冲到Log,然后只有当缓冲区达到一定大小时才将它们写入文件(它“刷新”缓冲区)。这可能比仅将所有内容立即转储到文件更具性能。当Logger被销毁时,你需要从缓冲区中清除所有内容,无论它是否已满,所以你可能想为它编写一个析构函数,即使它很容易根据std :: vector和std实现Logger。 :: string,以便在销毁时不会泄漏。

编辑:我没有看到问题2.问题2的答案是,如果它是非拥有指针,则不应调用delete。换句话说,如果某个其他类或范围单独负责在此对象之后进行清理,并且您的指针“只是为了查看”,则不要调用delete。原因是如果你调用delete而其他人拥有它,则指针会在其上调用两次删除:

struct A {
  A(SomeObj * obj) : m_obj(obj){};
  SomeObj * m_obj;
  ~A(){delete m_obj;};
}

SomeObj * obj = new SomeObj();
A a(obj);
delete obj; // bad!

事实上,可以说c ++ 11中的指南是永远不要在指针上调用delete。为什么?好吧,如果你在指针上调用delete,就意味着你拥有它。如果您拥有它,则没有理由不使用智能指针,特别是unique_ptr实际上是相同的速度并自动执行此操作,并且更可能是线程安全的。

此外,(请原谅我,我现在已经深入研究了这一点),对其他对象的对象(原始指针或引用)成员进行非拥有视图通常是个坏主意。为什么?因为具有原始指针的对象可能不必担心破坏另一个对象,因为它不拥有它,但它无法知道它何时会被销毁。当指针对象仍处于活动状态时,指向的对象可能会被破坏:

struct A {
  SomeObj * m_obj;
  void func(){m_obj->doStuff();};
}

A a;
if(blah) {
  SomeObj b;
  a.m_obj = &b;
}
a.func() // bad!

请注意,这仅适用于对象的成员字段。将对象的视图传递给函数(成员与否)是安全的,因为函数是在对象本身的封闭范围内调用的,所以这不是问题。

所有这一切的苛刻结论是,除非你知道自己在做什么,否则你不应该将原始指针或引用作为对象的成员字段。

编辑2:我猜总体结论(非常好!)一般来说,你的类应该以不需要析构函数的方式编写,除非析构函数在语义上有意义。在我的Logger示例中,必须刷新Logger,在销毁之前必须执行一些重要操作。你不应该写(通常)在成员之后需要进行简单清理的类,成员变量应该自己清理。

答案 1 :(得分:2)

一个类需要一个析构函数,它拥有"拥有"资源并负责清理它。析构函数的目的不仅仅是使类本身正常工作,而是使程序作为一个整体正常工作:如果需要清理资源,则需要执行某些操作,因此某些对象应该负责清理。

例如,可能需要释放内存。可能需要关闭文件句柄。可能需要关闭网络套接字。可能需要释放图形设备。如果没有明确销毁,这些东西将会存在,因此需要销毁它们。

析构函数的目的是将资源的生命周期与对象联系起来,以便在对象消失时资源消失。

答案 2 :(得分:1)

当您的类包含动态分配的内存时,析构函数非常有用。如果您的课程很简单并且没有“DAM”,那么不使用析构函数是安全的。另外,请阅读Rule Of Three。如果你的班级将要“DAM”,你还应该添加一个复制构造函数和一个重载的=运算符。

2)不要担心参考文献。它们以不同的方式工作,例如它“引用”另一个变量(这意味着它们不指向内存)。