問題描述
假設我們有一個(玩具)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).
這篇關于“空"構造函數或析構函數會與生成的構造函數做同樣的事情嗎?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!