【问题标题】:Why do C++ objects have a default destructor?为什么 C++ 对象有默认析构函数?
【发布时间】:2011-06-17 18:10:45
【问题描述】:

例如,当我不声明构造函数时,编译器将为我提供一个没有参数和定义(空体)的默认构造函数,因此,将不采取任何行动 .

那么,例如,如果我完成了一个对象,默认析构函数不会重新分配(空闲)该对象使用的内存吗?如果没有,我们为什么要得到它?

而且,也许同样的问题也适用于默认构造函数。如果它什么都不做,为什么会默认为我们创建呢?

【问题讨论】:

  • 构造函数没有分配对象的内存,在调用构造函数之前,内存已经以某种方式分配给它。
  • 提出这个问题时有很多错误的陈述。并且不接受指向他们的答案。

标签: c++ constructor destructor default-constructor


【解决方案1】:

说编译器生成的默认构造函数不采取任何行动是错误的。它相当于一个用户定义的构造函数,它有一个空的主体和一个空的初始化列表,但这并不意味着它不采取任何行动。它的作用如下:

  1. 它调用基类的默认构造函数。
  2. 如果类是多态的,它会初始化 vtable 指针。
  3. 它调用所有拥有它们的成员的默认构造函数。如果某个成员具有一些构造函数,但没有默认构造函数,则这是编译时错误。

只有当一个类不是多态的、没有基类并且没有需要构造的成员时,编译器生成的默认构造函数才不会执行任何操作。但即便如此,由于其他答案中解释的原因,有时也需要默认构造函数。

析构函数也是如此——它调用基类的析构函数和拥有它们的所有成员的析构函数,因此在一般情况下编译器生成的析构函数不执行任何操作是不正确的。

但内存分配确实与此无关。内存是在构造函数被调用之前分配的,只有在最后一个析构函数完成后才会释放。

【讨论】:

  • 公平地说,所有这些都发生在“幕后”。即使您没有在构造函数中编写任何代码,它也会发生在用户声明的构造函数中。
  • @Oli,当然,这正是我所说的“等效于具有空主体和空初始化列表的用户定义构造函数”的意思。我的观点很简单,在一般情况下,说它是一个无操作构造函数是错误的。
  • 我的answer 给出了一个示例,其中默认析构函数是避免内存泄漏的关键。
【解决方案2】:

因为如果您没有任何(可公开访问的)构造函数或析构函数,则无法实例化该类的对象。考虑:

class A
{
private:
    A() {}
    ~A() {}
};

A a;  // Oh dear!  Compilation error

如果您没有显式声明任何构造函数或析构函数,编译器必须提供一个以允许创建对象。

【讨论】:

  • 为什么会出现编译错误?不是已经有默认构造函数了吗?谢谢
  • @user:不,因为我们已将默认构造函数声明为私有。因此,它不能被调用(除了通过静态类成员函数)。
【解决方案3】:

使用智能指针时,默认析构函数(请参阅 Sergey 的回答)对于避免内存泄漏至关重要。举个例子:

#include <iostream>
#include <memory>

using namespace std;

class Foo {
public:
  Foo(int n = 0): n(n) { cout << "Foo(" << n << ")" << endl; }
  ~Foo() { cout << "~Foo(" << n << ")" << endl; }
private:
  int n;
};

// notes:
// * default destructor of Bar calls destructors of unique_ptr<Foo> foo
//  and of unique_ptr<Foo[]> foo3, which, in turn, delete the Foo objects
// * foo2's Foo object leaks
class Bar {
public:
  Bar(): foo(new Foo(1)), foo2(new Foo(2)), foo3(new Foo[2]) { }
private:
  unique_ptr<Foo> foo;
  Foo* foo2;
  unique_ptr<Foo[]> foo3;
};

int main() {
  Bar bar;
  cout << "in main()" << endl;
}

这里的输出,表明泄漏仅发生在foo2

Foo(1)
Foo(2)
Foo(0)
Foo(0)
in main()
~Foo(0)
~Foo(0)
~Foo(1)

【讨论】:

    【解决方案4】:

    默认析构函数不会做任何事情(就像默认构造函数一样)。

    如果您的析构函数确实需要做某事(例如:释放一些资源),您需要自己定义一个。

    请注意,通常您应该遵循rule of three:如果您的程序需要在其析构函数中执行某些操作(例如:释放资源),您还应该提供一个复制构造函数和一个赋值运算符; C++ 还提供了它们的默认版本(同样,它不会做任何事情)。

    当您处理不需要做任何事情的简单类时,默认构造函数/析构函数/赋值运算符/复制构造函数很有用。一个特殊情况是 POD:它们(在 C++0x 之前)甚至不能有显式的构造函数或析构函数。

    【讨论】:

      【解决方案5】:

      默认构造函数和析构函数只是一种商品,以防你不需要对你的类做任何特别的事情,你不需要手动编写一个空版本。这对于其他 OO 语言很常见,例如在 Java 中,如果成员的零初始化就足够了,则不需要提供构造函数。同时,它是向后兼容 C 的要求。如果您在 C 中有 struct,它将没有构造函数或析构函数(C 没有这些概念),以便能够在 C++ 中处理该代码必须是有效代码。

      另一种选择是在语言中声明一个类可以没有构造函数或析构函数,但是整个语言规范必须处理这样一个事实,即某些类型可能具有构造函数和析构函数,而其他类型则没有,并且这将使语言更复杂,更难指定。虽然具有隐式定义的版本不会改变行为并简化规范。

      在这个级别上,就像将术语 overrider 应用于基类中的方法一样。在基类中,它没有覆盖任何东西,没有什么可以覆盖的!然而,该语言明确指出,在基中声明的虚拟非纯方法是覆盖器。这使得规范可以简单地说 final overrider 在通过指针或引用调用方法时将被调用,而无需添加 extre * 或基本方法实现(如果该特定的覆盖器不存在)该特定层次结构中的方法。

      【讨论】:

        【解决方案6】:

        简短的回答是,在 C++ 中,每个对象都需要一个构造函数和一个析构函数,即使它们什么都不做。所以在后台为你创建它们的编译器就满足了这个要求。

        一个更长的答案是构造函数负责类成员的初始化。默认构造函数对所有成员进行默认初始化。 (这对于 POD 类型没有任何意义,但是其他类会调用它们的默认构造函数。)

        【讨论】:

          【解决方案7】:

          默认析构函数无法知道你的类“拥有”什么内存来释放它。

          至于默认构造函数部分,我将在这个部分引用Wikipedia article...

          在 C++ 中,默认构造函数很重要,因为它们是 在某些情况下自动调用 情况:

          • 当声明对象值时没有参数列表,例如我的课 X;;或动态分配,没有 参数列表,例如新的我的班级;这 默认构造函数用于 初始化对象
          • 当声明对象数组时,例如我的类 x[10];;要么 动态分配,例如新的 我的班级[10];默认构造函数 用于初始化所有元素
          • 当派生类构造函数没有显式调用基类时 初始化器中的类构造函数 列表,默认构造函数 基类被调用
          • 当类构造函数没有显式调用的构造函数时 其对象值字段之一 初始化列表,默认 字段类的构造函数是 叫
          • 在标准库中,某些容器使用 值为时的默认构造函数 没有明确给出,例如 向量(10);初始化 具有 10 个元素的向量,它们是 填充了默认构造的 我们类型的值。

          在上面 在这种情况下,如果 类没有默认值 构造函数。编译器将 隐式定义默认值 构造函数

          如果没有构造函数 为一个类显式定义。这 隐式声明的默认值 构造函数等价于默认值 用空白主体定义的构造函数。 (注意:如果某些构造函数是 已定义,但它们都是非默认的, 编译器不会隐式 定义一个默认构造函数。这 意味着默认构造函数可以 类不存在。)

          【讨论】:

            猜你喜欢
            • 2011-01-25
            • 2011-09-30
            • 2013-02-01
            • 1970-01-01
            • 2022-01-09
            • 1970-01-01
            • 1970-01-01
            • 2012-11-03
            • 1970-01-01
            相关资源
            最近更新 更多