它会做同样的事情(本质上什么都没有)。但这和你不写它是不一样的。因为编写析构函数需要一个有效的基类析构函数。如果基类析构函数是私有的,或者有任何其他原因不能调用它,那么你的程序就有问题。考虑这个
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();
};
假设 type 的对象C
是在文件中 A 的构造函数的定义中创建的,该.cpp
文件还包含 struct 的定义C
。现在,如果您使用 structA
并要求销毁A
对象,编译器将提供析构函数的隐式定义,就像上面的情况一样。该析构函数还将隐式调用 auto_ptr 对象的析构函数。这将删除它持有的指针,该指针指向C
对象 - 不知道C
! 这出现在.cpp
定义结构 A 的构造函数的文件中。
这实际上是实现 pimpl 习语的常见问题。.cpp
这里的解决方案是添加一个析构函数并在定义结构的文件中提供它的空定义C
。在它调用其成员的析构函数时,它将知道 struct 的定义C
,并且可以正确调用其析构函数。
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 之类的事情上,这些调用(即使实际上没有在运行时完成——纯粹的可能性在这里已经很重要了)将造成与析构函数相同的危害,并且在构造函数中抛出某些东西时发生——然后编译器需要调用析构函数的成员。这个答案使用了默认构造函数的隐式定义。
同样,对于我上面提到的析构函数的可见性和 PODness 也是如此。
关于初始化有一个重要的区别。如果您放置用户声明的构造函数,则您的类型不再接收成员的值初始化,并且由您的构造函数来执行所需的任何初始化。例子:
struct A {
int a;
};
struct B {
int b;
B() { }
};
在这种情况下,以下总是正确的
assert(A().a == 0);
虽然以下是未定义的行为,因为b
从未初始化(您的构造函数省略了)。该值可能为零,但也可能是任何其他奇怪的值。试图从这样一个未初始化的对象中读取会导致未定义的行为。
assert(B().b == 0);
在 中使用这种语法也是如此new
(new A()
注意末尾的括号 - 如果省略它们,则不进行值初始化,并且由于没有用户声明的构造函数可以对其进行初始化,因此a
将保持未初始化状态)。