C++ “空”构造函数或析构函数会与生成的构造函数做同样的事情吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1025313/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Will an 'empty' constructor or destructor do the same thing as the generated one?
提问by Andrew Song
Suppose we have a (toy) C++ class such as the following:
假设我们有一个(玩具)C++ 类,如下所示:
class Foo {
public:
Foo();
private:
int t;
};
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.
由于没有定义析构函数,C++ 编译器应该自动为 class 创建一个Foo
。如果析构函数不需要清理任何动态分配的内存(也就是说,我们可以合理地依赖编译器给我们的析构函数),则将定义一个空的析构函数,即。
Foo::~Foo() { }
do the same thing as the compiler-generated one? What about an empty constructor -- that is, Foo::Foo() { }
?
和编译器生成的一样吗?一个空的构造函数Foo::Foo() { }
呢——也就是说,?
If there are differences, where do they exist? If not, is one method preferred over the other?
如果存在差异,它们存在于何处?如果不是,一种方法是否优于另一种方法?
回答by Johannes Schaub - litb
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 { };
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
没关系,只要您不需要销毁 B 类型的对象(因此,隐式为 A 类型)-就像您从不对动态创建的对象调用 delete 一样,或者您从不在其中创建它的对象第一名。如果这样做,编译器将显示适当的诊断信息。现在,如果您明确提供一个
struct A { private: ~A(); };
struct B : A { ~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
.
那个会尝试隐式调用基类的析构函数,并且会在~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();
};
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.
让我们假设 type 的对象C
是在.cpp
文件中 A 的构造函数的定义中创建的,该文件也包含 struct 的定义C
。现在,如果您使用 struct A
,并且需要销毁一个A
对象,编译器将提供析构函数的隐式定义,就像上面的情况一样。该析构函数还将隐式调用 auto_ptr 对象的析构函数。这将删除它持有的指向C
对象的指针- 不知道C
! 这出现在.cpp
定义 struct A 的构造函数的文件中。
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.
这实际上是实现 pimpl 成语的常见问题。这里的解决方案是添加一个析构函数并在定义.cpp
结构体的文件中提供它的空C
定义。在它调用其成员的析构函数时,它就会知道 struct 的定义C
,并可以正确调用其析构函数。
struct C;
struct A {
auto_ptr<C> a;
A();
~A(); // defined as ~A() { } in .cpp file, too
};
Note that boost::shared_ptr
does not have that problem: It instead requires a complete type when its constructor is invoked in certain ways.
请注意,boost::shared_ptr
它没有这个问题:当它的构造函数以某些方式被调用时,它需要一个完整的类型。
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.
它在当前 C++ 中产生差异的另一点是当您想要memset
在具有用户声明的析构函数的对象上使用和好友时。这些类型不再是 POD(纯旧数据),并且不允许进行位复制。请注意,这个限制并不是真正需要的 - 下一个 C++ 版本已经改善了这种情况,因此只要不进行其他更重要的更改,它仍然允许您对此类类型进行位复制。
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 answermakes some use of implicit definition of default constructors.
既然你要求构造函数:嗯,对于这些事情来说,事情都是一样的。请注意,构造函数还包含对析构函数的隐式调用。在 auto_ptr 之类的东西上,这些调用(即使实际上没有在运行时完成 - 纯粹的可能性在这里已经很重要)将造成与析构函数相同的危害,并且在构造函数中的某些内容抛出时发生 - 然后需要编译器调用析构函数的成员。这个答案使用了默认构造函数的隐式定义。
Also, the same is true for visibility and PODness that i said about the destructor above.
此外,我在上面所说的关于析构函数的可见性和 POD 性也是如此。
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);
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.
虽然以下是未定义的行为,因为b
从未初始化(您的构造函数省略了它)。该值可能为零,但也可能是任何其他奇怪的值。试图从这样一个未初始化的对象中读取会导致未定义的行为。
assert(B().b == 0);
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).
在 中使用此语法也是如此new
,例如new A()
(注意末尾的括号 - 如果省略它们,则不会进行值初始化,并且由于没有用户声明的构造函数可以对其进行初始化,a
因此将保持未初始化状态)。
回答by Gregory Pakosz
I know I'm late in the discussion, nevertheless my experience says that the compiler behaves differently when facing an empty destructor compared to a compiler generated one. At least this is the case with MSVC++ 8.0 (2005) and MSVC++ 9.0 (2008).
我知道我在讨论中迟到了,但是我的经验表明,与编译器生成的析构函数相比,编译器在面对空析构函数时的行为有所不同。至少 MSVC++ 8.0 (2005) 和 MSVC++ 9.0 (2008) 就是这种情况。
When looking at the generated assembly for some code making use of expression templates, I realized that in release mode, the call to my BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
was never inlined. (please don't pay attention to the exact types and operator signature).
在查看使用表达式模板的某些代码生成的程序集时,我意识到在发布模式下,对 my 的调用BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
从未被内联。(请不要注意确切的类型和运算符签名)。
To further diagnose the problem, I enabled the various Compiler Warnings That Are Off by Default. The C4714warning is particularly interesting. It is emitted by the compiler when a function marked with __forceinline
doesn't get inlined nonetheless.
为了进一步诊断问题,我启用了默认关闭的各种编译器警告。该C4714警告是特别有趣。当标记为 的函数__forceinline
仍然没有被内联时,它由编译器发出。
I enabled the C4714 warning and I marked the operator with __forceinline
and I could verify the compiler reports it was unable to inline the call to the operator.
我启用了 C4714 警告,并将操作员标记为__forceinline
,我可以验证编译器报告它无法内联对操作员的调用。
Among the reasons described in the documentation, the compiler fails to inline a function marked with __forceinline
for:
在文档中描述的原因中,编译器无法内联标记__forceinline
为 for的函数:
Functions returning an unwindable object by value when -GX/EHs/EHa is on
当 -GX/EHs/EHa 开启时,函数按值返回一个可展开的对象
This is the case of my BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
. BinaryVectorExpression
is returned by value and even though its destructor is empty, it makes this return value being considered as an unwindable object. Adding throw ()
to the destructor didn't help the compiler and I avoid using exception specifications anyway. Commenting out the empty destructor let the compiler fully inline the code.
这是我的BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
. BinaryVectorExpression
按值返回,即使其析构函数为空,它也会将此返回值视为可展开的对象。添加throw ()
到析构函数对编译器没有帮助,无论如何我都避免使用异常规范。注释掉空的析构函数让编译器完全内联代码。
The take-away is that from now, in every class, I write commented out empty destructors to let humans know the destructor does nothing on purpose, the very same way people comment out the empty exception specification `/* throw() */ to indicate that the destructor cannot throw.
要点是,从现在开始,在每个类中,我都会写出注释掉的空析构函数,让人们知道析构函数是故意不做任何事情的,就像人们注释掉空异常规范 `/* throw() */ 到表示析构函数不能抛出。
//~Foo() /* throw() */ {}
Hope that helps.
希望有帮助。
回答by Faisal Vali
The empty destructor that you defined out of class has similar semantics in most regards, but not in all.
您在类外定义的空析构函数在大多数方面具有相似的语义,但并非全部如此。
Specifically, the implicitly defined destructor
1) is an inlinepublic member (yours is not inline)
2) is denoted as a trivial destructor (necessary to make trivial types that can be in unions, yours cannot)
3) has an exception specification (throw(), yours does not)
具体来说,隐式定义的析构函数
1) 是一个内联公共成员(你的不是内联的)
2)被表示为一个平凡的析构函数(需要制作可以在联合中的平凡类型,你的不能)
3)有一个异常规范(抛出(),你的没有)
回答by David Seiler
Yes, that empty destructor is the same as the automatically-generated one. I've always just let the compiler generate them automatically; I don't think it's necessary to specify the destructor explicitly unless you need to do something unusual: make it virtual or private, say.
是的,那个空的析构函数与自动生成的析构函数相同。我总是让编译器自动生成它们;我认为没有必要显式指定析构函数,除非你需要做一些不寻常的事情:比如让它成为虚拟的或私有的。
回答by oscarkuo
I agree with David except that I would say it is generally a good practice to define a virtual destructor i.e.
我同意大卫的观点,但我会说定义虚拟析构函数通常是一个好习惯,即
virtual ~Foo() { }
missing out virtual destructor can lead to memory leak because people who inherit from your Foo class may not have noticed that their destructor will never be called!!
错过虚拟析构函数会导致内存泄漏,因为从你的 Foo 类继承的人可能没有注意到他们的析构函数永远不会被调用!!
回答by Ape-inago
I'd say best to put the empty declaration, it tells any future maintainers that it wasn't an oversight, and you really did mean to use the default one.
我会说最好放置空声明,它告诉任何未来的维护者这不是疏忽,而且您确实打算使用默认声明。
回答by Ape-inago
An empty definition is fine since the definition can be referenced
空定义也可以,因为可以引用定义
virtual ~GameManager() { };
空声明在外观上看似相似virtual ~GameManager();却邀请了可怕的人 no definition for virtual destructor没有对虚拟析构函数错误的定义
Undefined symbols:
"vtable for GameManager", referenced from:
__ZTV11GameManager$non_lazy_ptr in GameManager.o
__ZTV11GameManager$non_lazy_ptr in Main.o
ld: symbol(s) not found