问题描述
假设我们有一个(玩具)C++ 类,如下所示:
Suppose we have a (toy) C++ class such as the following:
class Foo {
public:
Foo();
private:
int t;
};
由于没有定义析构函数,C++编译器应该自动为类Foo
创建一个.如果析构函数不需要清理任何动态分配的内存(也就是说,我们可以合理地依赖编译器给我们的析构函数),则会定义一个空的析构函数,即.
Since no destructor is defined, a C++ compiler should create one automatically for class Foo
. If the destructor does not need to clean up any dynamically allocated memory (that is, we could reasonably rely on the destructor the compiler gives us), will defining an empty destructor, ie.
Foo::~Foo() { }
和编译器生成的一样吗?一个空的构造函数呢——也就是说,Foo::Foo() { }
?
do the same thing as the compiler-generated one? What about an empty constructor -- that is, Foo::Foo() { }
?
如果存在差异,它们存在于何处?如果不是,一种方法是否优于另一种方法?
If there are differences, where do they exist? If not, is one method preferred over the other?
推荐答案
它会做同样的事情(本质上什么都没有).但这和没写不一样.因为编写析构函数将需要一个工作的基类析构函数.如果基类析构函数是私有的或者有任何其他原因无法调用它,那么你的程序就有问题.考虑这个
It will do the same thing (nothing, in essence). But it's not the same as if you didn't write it. Because writing the destructor will require a working base-class destructor. If the base class destructor is private or if there is any other reason it can't be invoked, then your program is faulty. Consider this
struct A { private: ~A(); };
struct B : A { };
没关系,只要您不需要销毁 B 类型的对象(因此,隐式为 A 类型) - 就像您从不对动态创建的对象调用 delete 一样,或者您从不创建对象它首先.如果这样做,则编译器将显示适当的诊断信息.现在,如果您明确提供一个
That is OK, as long as your don't require to destruct an object of type B (and thus, implicitly of type A) - like if you never call delete on a dynamically created object, or you never create an object of it in the first place. If you do, then the compiler will display an appropriate diagnostic. Now if you provide one explicitly
struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } };
那个会尝试隐式调用基类的析构函数,并且会在 ~B
的定义时间导致诊断.
That one will try to implicitly call the destructor of the base-class, and will cause a diagnostic already at definition time of ~B
.
还有一个区别在于析构函数的定义和对成员析构函数的隐式调用.考虑这个智能指针成员
There is another difference that centers around the definition of the destructor and implicit calls to member destructors. Consider this smart pointer member
struct C;
struct A {
auto_ptr<C> a;
A();
};
假设C
类型的对象是在.cpp
文件中A的构造函数定义中创建的,该文件也包含structC<的定义/代码>.现在,如果您使用 struct
A
,并且需要销毁 A
对象,则编译器将提供析构函数的隐式定义,就像上面的情况一样.该析构函数还将隐式调用 auto_ptr 对象的析构函数.这将删除它持有的指针,该指针指向 C
对象 - 不知道 C
的定义!出现在 .cpp
文件中,其中定义了 struct A 的构造函数.
Let's assume the object of type C
is created in the definition of A's constructor in the .cpp
file, which also contains the definition of struct C
. Now, if you use struct A
, and require destruction of an A
object, the compiler will provide an implicit definition of the destructor, just like in the case above. That destructor will also implicitly call the destructor of the auto_ptr object. And that will delete the pointer it holds, that points to the C
object - without knowing the definition of C
! That appeared in the .cpp
file where struct A's constructor is defined.
这实际上是实现 pimpl 成语的一个常见问题.这里的解决方案是添加一个析构函数并在 .cpp
文件中提供它的空定义,其中定义了结构体 C
.当它调用其成员的析构函数时,它就会知道struct C
的定义,并且可以正确调用它的析构函数.
This actually is a common problem in implementing the pimpl idiom. The solution here is to add a destructor and provide an empty definition of it in the .cpp
file, where the struct C
is defined. At the time it invokes the destructor of its member, it will then know the definition of struct C
, and can correctly call its destructor.
struct C;
struct A {
auto_ptr<C> a;
A();
~A(); // defined as ~A() { } in .cpp file, too
};
请注意,boost::shared_ptr
没有这个问题:当它的构造函数以某些方式被调用时,它需要一个完整的类型.
Note that boost::shared_ptr
does not have that problem: It instead requires a complete type when its constructor is invoked in certain ways.
它在当前 C++ 中的另一个不同点是当您想在具有用户声明的析构函数的对象上使用 memset
和朋友时.这些类型不再是 POD(纯旧数据),并且不允许进行位复制.请注意,这种限制并不是真正需要的 - 下一个 C++ 版本已经改善了这种情况,因此只要不进行其他更重要的更改,它仍然允许您对此类类型进行位复制.
Another point where it makes a difference in current C++ is when you want to use memset
and friends on such an object that has a user declared destructor. Such types are not PODs anymore (plain old data), and these are not allowed to be bit-copied. Note that this restriction isn't really needed - and the next C++ version has improved the situation on this, so that it allows you to still bit-copy such types, as long as other more important changes are not made.
既然你要求构造函数:嗯,对于这些事情来说都是一样的.请注意,构造函数还包含对析构函数的隐式调用.在像 auto_ptr 这样的东西上,这些调用(即使实际上没有在运行时完成——这里纯粹的可能性已经很重要了)将造成与析构函数相同的危害,并且在构造函数中的某些东西抛出时发生——然后编译器需要调用析构函数的成员.这个答案使用默认构造函数的隐式定义.
Since you asked for constructors: Well, for these much the same things are true. Note that constructors also contain implicit calls to destructors. On things like auto_ptr, these calls (even if not actually done at runtime - the pure possibility already matters here) will do the same harm as for destructors, and happen when something in the constructor throws - the compiler is then required to call the destructor of the members. This answer makes some use of implicit definition of default constructors.
此外,我在上面提到的析构函数的可见性和 POD 性也是如此.
Also, the same is true for visibility and PODness that i said about the destructor above.
关于初始化有一个重要区别.如果您放置了一个用户声明的构造函数,您的类型将不再接收成员的值初始化,并且由您的构造函数进行任何需要的初始化.示例:
There is one important difference regarding initialization. If you put a user declared constructor, your type does not receive value initialization of members anymore, and it is up to your constructor to do any initialization that's needed. Example:
struct A {
int a;
};
struct B {
int b;
B() { }
};
在这种情况下,以下总是正确的
In this case, the following is always true
assert(A().a == 0);
虽然以下是未定义的行为,因为 b
从未被初始化(您的构造函数省略了它).该值可能为零,但也可能是任何其他奇怪的值.试图从这样一个未初始化的对象中读取会导致未定义的行为.
While the following is undefined behavior, because b
was never initialized (your constructor omitted that). The value may be zero, but may aswell be any other weird value. Trying to read from such an uninitialized object causes undefined behavior.
assert(B().b == 0);
在 new
中使用这种语法也是如此,例如 new A()
(注意末尾的括号 - 如果省略它们,则不会进行值初始化,并且由于没有用户声明的构造函数可以初始化它,a
将保持未初始化).
This is also true for using this syntax in new
, like new A()
(note the parentheses at the end - if they are omitted value initialization is not done, and since there is no user declared constructor that could initialize it, a
will be left uninitialized).
这篇关于“空"构造函数或析构函数会与生成的构造函数做同样的事情吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!