例如,当我不声明构造函数时,编译器将为我提供一个没有参数和定义(空体)的默认构造函数,因此不会采取任何行动。
那么,例如,如果我完成了一个对象,默认析构函数不会重新分配(空闲)对象使用的内存吗?如果没有,我们为什么要得到它?
而且,也许同样的问题也适用于默认构造函数。如果它什么都不做,为什么默认为我们创建它?
例如,当我不声明构造函数时,编译器将为我提供一个没有参数和定义(空体)的默认构造函数,因此不会采取任何行动。
那么,例如,如果我完成了一个对象,默认析构函数不会重新分配(空闲)对象使用的内存吗?如果没有,我们为什么要得到它?
而且,也许同样的问题也适用于默认构造函数。如果它什么都不做,为什么默认为我们创建它?
说编译器生成的默认构造函数不采取任何行动是错误的。它相当于一个用户定义的构造函数,它有一个空的主体和一个空的初始化列表,但这并不意味着它不采取任何行动。这是它的作用:
并且只有当一个类不是多态的、没有基类并且没有需要构造的成员时,编译器生成的默认构造函数才不会执行任何操作。但即便如此,由于其他答案中解释的原因,有时也需要默认构造函数。
析构函数也是如此——它调用基类的析构函数和拥有它们的所有成员的析构函数,因此在一般情况下编译器生成的析构函数什么都不做是不正确的。
但是内存分配确实与此无关。内存在调用构造函数之前分配,只有在最后一个析构函数完成后才会释放。
因为如果您没有任何(可公开访问的)构造函数或析构函数,则无法实例化该类的对象。考虑:
class A
{
private:
A() {}
~A() {}
};
A a; // Oh dear! Compilation error
如果您没有显式声明任何构造函数或析构函数,编译器必须提供一个以允许创建对象。
使用智能指针时,默认析构函数(参见 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)
默认析构函数不会做任何事情(就像默认构造函数一样)。
如果您的析构函数确实需要做某事(例如:释放一些资源),您需要自己定义一个。
请注意,通常您应该遵循三原则:如果您的程序需要在其析构函数中执行某些操作(例如:释放资源),您还应该提供一个复制构造函数和一个赋值运算符;C++ 还提供了它们的默认版本(同样,它不会做任何事情)。
当您处理不需要做任何事情的简单类时,默认构造函数/析构函数/赋值运算符/复制构造函数很有用。一个特殊情况是 POD:它们(在 C++0x 之前)甚至不能具有显式构造函数或析构函数。
默认构造函数和析构函数只是一种商品,以防您不需要对您的类进行任何特殊处理,您不需要手动编写空版本。这对于其他 OO 语言很常见,例如在 Java 中,如果成员的零初始化就足够了,则不需要提供构造函数。同时它是与 C 向后兼容的要求。如果你struct
在 C 中有一个,它将没有构造函数或析构函数(C 没有这些概念),以便能够在 C++ 中处理必须是有效代码。
另一种选择是在语言中声明一个类可以没有构造函数或析构函数,但是整个语言规范必须处理这样一个事实,即某些类型可能具有构造函数和析构函数,而其他类型则没有,这将使语言更复杂,更难指定。虽然具有隐式定义的版本不会改变行为并简化规范。
在这个级别上,它就像术语覆盖器被应用于基类中的方法。在基类中,它没有覆盖任何东西,没有任何东西可以覆盖!然而,该语言明确指出,在基中声明的虚拟非纯方法是覆盖器。这使得规范可以简单地说,当通过指针或引用调用方法时将调用最终覆盖器,而无需添加 extre * 或基方法实现,如果该特定层次结构中该特定方法不存在覆盖器。
简短的回答是,在 C++ 中,每个对象都需要一个构造函数和一个析构函数,即使它们什么都不做。所以在后台为你创建它们的编译器就满足了这个要求。
更长的答案是构造函数负责类成员的初始化。默认构造函数对所有成员进行默认初始化。(这对于 POD 类型没有任何意义,但其他类会调用它们的默认构造函数。)
默认析构函数无法知道您的类“拥有”什么内存才能释放它。
至于默认构造函数部分,我将引用维基百科的文章...
在 C++ 中,默认构造函数很重要,因为它们在某些情况下会自动调用:
- 当声明一个没有参数列表的对象值时,例如 MyClass x;; 或不带参数列表动态分配,例如 new MyClass;默认构造函数用于初始化对象
- 当声明一个对象数组时,例如 MyClass x[10];; 或动态分配,例如 new MyClass [10];默认构造函数用于初始化所有元素
- 当派生类构造函数没有显式调用其初始化列表中的基类构造函数时,将调用基类的默认构造函数
- 当类构造函数没有显式调用其初始值设定项列表中其对象值字段之一的构造函数时,将调用该字段类的默认构造函数
- 在标准库中,某些容器在没有明确给出值时使用默认构造函数“填充”值,例如 vector(10);用 10 个元素初始化向量,这些元素填充了我们类型的默认构造值。
在上述情况下,如果类没有默认构造函数是错误的。编译器会隐式定义一个默认构造函数
如果没有为类显式定义构造函数。这个隐式声明的默认构造函数等效于用空白主体定义的默认构造函数。(注意:如果定义了一些构造函数,但它们都是非默认的,编译器不会隐式定义默认构造函数。这意味着一个类可能不存在默认构造函数。)