它会做同样的事情(本质上什么都没有)。但这和你不写它是不一样的。因为编写析构函数需要一个有效的基类析构函数。如果基类析构函数是私有的,或者有任何其他原因不能调用它,那么你的程序就有问题。考虑一下这个
struct A { private: ~A(); };
struct B : A { };
没关系,只要您不需要破坏 B 类型的对象(因此,隐式 A 类型) - 就像您从不对动态创建的对象调用 delete,或者您从不创建对象首先是它。如果这样做,编译器将显示适当的诊断信息。现在,如果您明确提供一个
struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } };
这将尝试隐式调用基类的析构函数,并在~B 的定义时间引起诊断。
还有一个区别在于析构函数的定义和对成员析构函数的隐式调用。考虑这个智能指针成员
struct C;
struct A {
auto_ptr<C> a;
A();
};
假设C类型的对象是在.cpp文件中A的构造函数定义中创建的,该文件还包含structC的定义。现在,如果您使用 struct A,并要求销毁 A 对象,编译器将提供析构函数的隐式定义,就像上面的情况一样。该析构函数还将隐式调用 auto_ptr 对象的析构函数。这将删除它持有的指针,该指针指向 C 对象 - 不知道 C 的定义!这出现在定义结构 A 的构造函数的 .cpp 文件中。
这实际上是实现 pimpl 习语的常见问题。这里的解决方案是添加一个析构函数并在.cpp 文件中提供一个空的定义,其中定义了结构C。在它调用其成员的析构函数时,它就会知道structC的定义,并且可以正确地调用它的析构函数。
struct C;
struct A {
auto_ptr<C> a;
A();
~A(); // defined as ~A() { } in .cpp file, too
};
注意boost::shared_ptr 没有这个问题:当它的构造函数以某种方式被调用时,它需要一个完整的类型。
在当前 C++ 中有所不同的另一点是,当您想在具有用户声明的析构函数的此类对象上使用 memset 和朋友时。此类类型不再是 POD(普通旧数据),并且不允许进行位复制。请注意,这个限制并不是真正需要的——下一个 C++ 版本已经改进了这方面的情况,因此它允许您仍然对这些类型进行位复制,只要不进行其他更重要的更改。
既然你要求构造函数:嗯,对于这些来说,同样的事情是正确的。请注意,构造函数还包含对析构函数的隐式调用。在诸如 auto_ptr 之类的事情上,这些调用(即使实际上没有在运行时完成——纯粹的可能性在这里已经很重要了)将对析构函数造成同样的伤害,并且当构造函数中的某些东西抛出时发生——然后编译器需要调用析构函数的成员。 This answer 使用了默认构造函数的隐式定义。
此外,对于我上面提到的析构函数的可见性和 PODness 也是如此。
关于初始化有一个重要的区别。如果您放置用户声明的构造函数,则您的类型不再接收成员的值初始化,并且由您的构造函数执行任何所需的初始化。示例:
struct A {
int a;
};
struct B {
int b;
B() { }
};
在这种情况下,以下总是正确的
assert(A().a == 0);
虽然以下是未定义的行为,因为 b 从未初始化(您的构造函数省略了)。该值可能为零,但也可能是任何其他奇怪的值。试图从这样一个未初始化的对象中读取会导致未定义的行为。
assert(B().b == 0);
在new 中使用此语法也是如此,例如new A()(请注意末尾的括号 - 如果省略它们,则不会进行值初始化,因为没有用户声明的构造函数可以初始化它, a 将保持未初始化)。