問題描述
C++ 中的對(duì)象究竟何時(shí)被銷毀,這意味著什么?由于沒有垃圾收集器,我是否必須手動(dòng)銷毀它們?異常如何發(fā)揮作用?
(注意:這是Stack Overflow 的 C++ 常見問題解答的條目.如果您想批評(píng)以這種形式提供常見問題解答,然后 開始在元數(shù)據(jù)上發(fā)帖所有這些都可以做到這一點(diǎn).C++ 聊天室中監(jiān)控該問題的答案a>,F(xiàn)AQ 的想法最初是從這里開始的,因此您的答案很可能會(huì)被提出該想法的人閱讀.)
在下面的文本中,我將區(qū)分作用域?qū)ο?/em>,它們的銷毀時(shí)間由它們的封閉作用域(函數(shù)、塊、類、表達(dá)式)和動(dòng)態(tài)對(duì)象,它們的確切銷毀時(shí)間通常要到運(yùn)行時(shí)才能知道.
雖然類對(duì)象的銷毀語義由析構(gòu)函數(shù)決定,但標(biāo)量對(duì)象的銷毀始終是空操作.具體來說,銷毀指針變量不會(huì)銷毀指針對(duì)象.
作用域?qū)ο?/h1>自動(dòng)對(duì)象
當(dāng)控制流離開其定義的范圍時(shí),自動(dòng)對(duì)象(通常稱為局部變量")將按照其定義的相反順序進(jìn)行銷毀:
void some_function(){福一;福 b;如果(some_condition){傅y;福茲;} <--- z 和 y 在這里被破壞} <--- b 和 a 在這里被破壞
如果在函數(shù)執(zhí)行過程中拋出異常,則在異常傳播到調(diào)用者之前,所有先前構(gòu)造的自動(dòng)對(duì)象都會(huì)被銷毀.這個(gè)過程稱為堆棧展開.在堆棧展開期間,前面提到的自動(dòng)對(duì)象的析構(gòu)函數(shù)不會(huì)再有任何異常.否則,函數(shù) std::terminate
被調(diào)用.
這引出了 C++ 中最重要的準(zhǔn)則之一:
<塊引用>析構(gòu)函數(shù)不應(yīng)該拋出.
非本地靜態(tài)對(duì)象
在命名空間范圍內(nèi)定義的靜態(tài)對(duì)象(通常稱為全局變量")和靜態(tài)數(shù)據(jù)成員在執(zhí)行main
后按照其定義的相反順序被銷毀:
struct X{靜態(tài) Foo x;//這只是一個(gè)*聲明*,而不是*定義*};福一;福 b;int main(){} <--- y, x, b 和 a 在這里被破壞福 X::x;//這是各自的定義傅y;
請(qǐng)注意,在不同翻譯單元中定義的靜態(tài)對(duì)象的構(gòu)造(和銷毀)的相對(duì)順序是未定義的.
如果異常離開靜態(tài)對(duì)象的析構(gòu)函數(shù),則調(diào)用函數(shù)std::terminate
.
本地靜態(tài)對(duì)象
在函數(shù)內(nèi)部定義的靜態(tài)對(duì)象是在(如果)控制流第一次通過它們的定義時(shí)構(gòu)造的.1main
執(zhí)行后,它們以相反的順序銷毀:
Foo&get_some_Foo(){靜態(tài) Foo x;返回 x;}酒吧&get_some_Bar(){靜態(tài)酒吧 y;返回 y;}int main(){get_some_Bar().do_something();//注意 get_some_Bar 被稱為 *first*get_some_Foo().do_something();} <--- x 和 y 在這里被破壞//因此 y 被破壞 *last*
如果異常離開靜態(tài)對(duì)象的析構(gòu)函數(shù),則調(diào)用函數(shù)std::terminate
.
1:這是一個(gè)極其簡化的模型.靜態(tài)對(duì)象的初始化細(xì)節(jié)其實(shí)要復(fù)雜得多.
基類子對(duì)象和成員子對(duì)象
當(dāng)控制流離開對(duì)象的析構(gòu)函數(shù)體時(shí),其成員子對(duì)象(也稱為其數(shù)據(jù)成員")按照其定義的相反順序進(jìn)行析構(gòu).之后,它的基類子對(duì)象按照基類說明符列表的相反順序銷毀:
class Foo : Bar, Baz{庫克斯 x;曲y;上市:~Foo(){} <--- y 和 x 在這里被破壞,};其次是 Baz 和 Bar 基類子對(duì)象
如果在 Foo
的子對(duì)象之一的構(gòu)造過程中拋出異常,那么在傳播異常之前,其先前構(gòu)造的所有子對(duì)象都將被銷毀.另一方面,Foo
析構(gòu)函數(shù)將不會(huì)被執(zhí)行,因?yàn)?Foo
對(duì)象從未被完全構(gòu)造過.
請(qǐng)注意,析構(gòu)函數(shù)體不負(fù)責(zé)析構(gòu)數(shù)據(jù)成員本身.如果數(shù)據(jù)成員是對(duì)象銷毀時(shí)需要釋放的資源的句柄(例如文件、套接字、數(shù)據(jù)庫連接、互斥鎖或堆內(nèi)存),則只需編寫析構(gòu)函數(shù).>
數(shù)組元素
數(shù)組元素按降序銷毀.如果在第 n 個(gè)元素的構(gòu)造過程中拋出異常,則在傳播異常之前先銷毀第 n-1 到 0 個(gè)元素.
臨時(shí)對(duì)象
當(dāng)評(píng)估類類型的純右值表達(dá)式時(shí),會(huì)構(gòu)造一個(gè)臨時(shí)對(duì)象.prvalue 表達(dá)式最突出的例子是調(diào)用按值返回對(duì)象的函數(shù),例如 T operator+(const T&, const T&)
.一般情況下,當(dāng)詞法上包含純右值的完整表達(dá)式被完全求值時(shí),臨時(shí)對(duì)象就會(huì)被破壞:
__________________________ 完整表達(dá)式___________子表達(dá)式_______ 子表達(dá)式some_function(a + " " + b);^ 兩個(gè)臨時(shí)對(duì)象都在這里被破壞
上面的函數(shù)調(diào)用 some_function(a + " " + b)
是一個(gè)完整的表達(dá)式,因?yàn)樗皇歉蟊磉_(dá)式的一部分(相反,它是表達(dá)式語句的一部分).因此,在評(píng)估子表達(dá)式期間構(gòu)造的所有臨時(shí)對(duì)象都將在分號(hào)處銷毀.有兩個(gè)這樣的臨時(shí)對(duì)象:第一個(gè)是在第一次加法時(shí)構(gòu)造的,第二個(gè)是在第二次加法時(shí)構(gòu)造的.第二個(gè)臨時(shí)對(duì)象將在第一個(gè)之前銷毀.
如果在第二次添加過程中拋出異常,第一個(gè)臨時(shí)對(duì)象將在傳播異常之前正確銷毀.
如果局部引用是用純右值表達(dá)式初始化的,臨時(shí)對(duì)象的生命周期會(huì)擴(kuò)展到局部引用的范圍,所以你不會(huì)得到一個(gè)懸空引用:
<代碼>{const Foo&r = a + " " + b;^ 第一個(gè)臨時(shí) (a + " ") 在這里被破壞//...} <--- 第二個(gè)臨時(shí) (a + " " + b) 直到這里才被破壞
如果計(jì)算非類類型的純右值表達(dá)式,結(jié)果是一個(gè)值,而不是一個(gè)臨時(shí)對(duì)象.但是,如果使用純右值來初始化引用,則將構(gòu)造一個(gè)臨時(shí)對(duì)象:
const int&r = i + j;
動(dòng)態(tài)對(duì)象和數(shù)組
在下一節(jié)中,destroy X 的意思是先銷毀 X,然后釋放底層內(nèi)存".類似地,create X 的意思是先分配足夠的內(nèi)存,然后在那里構(gòu)造 X".
動(dòng)態(tài)對(duì)象
通過p = new Foo
創(chuàng)建的動(dòng)態(tài)對(duì)象通過delete p
銷毀.如果你忘記delete p
,你就有了資源泄漏.您永遠(yuǎn)不應(yīng)嘗試執(zhí)行以下操作之一,因?yàn)樗鼈兌紩?huì)導(dǎo)致未定義的行為:
- 通過
delete[]
(注意方括號(hào))、free
或任何其他方式銷毀動(dòng)態(tài)對(duì)象 - 多次銷毀動(dòng)態(tài)對(duì)象
- 在動(dòng)態(tài)對(duì)象被銷毀后訪問它
如果在動(dòng)態(tài)對(duì)象的構(gòu)造過程中拋出異常,則在傳播異常之前釋放底層內(nèi)存.(析構(gòu)函數(shù)將不會(huì)在內(nèi)存釋放之前執(zhí)行,因?yàn)閷?duì)象從未完全構(gòu)造過.)
動(dòng)態(tài)數(shù)組
通過p = new Foo[n]
創(chuàng)建的動(dòng)態(tài)數(shù)組通過delete[] p
銷毀(注意方括號(hào)).如果你忘記delete[] p
,你就有了資源泄漏.您永遠(yuǎn)不應(yīng)嘗試執(zhí)行以下操作之一,因?yàn)樗鼈兌紩?huì)導(dǎo)致未定義的行為:
- 通過
delete
、free
或任何其他方式銷毀動(dòng)態(tài)數(shù)組 - 多次銷毀動(dòng)態(tài)數(shù)組
- 在動(dòng)態(tài)數(shù)組被銷毀后訪問它
如果在第n個(gè)元素的構(gòu)造過程中拋出異常,則按降序銷毀n-1到0個(gè)元素,釋放底層內(nèi)存,并傳播異常.
(對(duì)于動(dòng)態(tài)數(shù)組,您通常應(yīng)該更喜歡 std::vector
Foo*
.它使編寫正確和健壯的代碼變得更加容易.)
引用計(jì)數(shù)智能指針
一個(gè)由多個(gè)std::shared_ptr
對(duì)象管理的動(dòng)態(tài)對(duì)象在最后一個(gè)參與共享的std::shared_ptr
對(duì)象銷毀過程中被銷毀那個(gè)動(dòng)態(tài)對(duì)象.
(對(duì)于共享對(duì)象,您通常應(yīng)該更喜歡 std::shared_ptr<Foo>
而不是 Foo*
.它使編寫正確和健壯的代碼變得更加容易.)
When exactly are objects destroyed in C++, and what does that mean? Do I have to destroy them manually, since there is no Garbage Collector? How do exceptions come into play?
(Note: This is meant to be an entry to Stack Overflow's C++ FAQ. If you want to critique the idea of providing an FAQ in this form, then the posting on meta that started all this would be the place to do that. Answers to that question are monitored in the C++ chatroom, where the FAQ idea started out in the first place, so your answer is very likely to get read by those who came up with the idea.)
In the following text, I will distinguish between scoped objects, whose time of destruction is statically determined by their enclosing scope (functions, blocks, classes, expressions), and dynamic objects, whose exact time of destruction is generally not known until runtime.
While the destruction semantics of class objects are determined by destructors, the destruction of a scalar object is always a no-op. Specifically, destructing a pointer variable does not destroy the pointee.
Scoped objects
automatic objects
Automatic objects (commonly referred to as "local variables") are destructed, in reverse order of their definition, when control flow leaves the scope of their definition:
void some_function()
{
Foo a;
Foo b;
if (some_condition)
{
Foo y;
Foo z;
} <--- z and y are destructed here
} <--- b and a are destructed here
If an exception is thrown during the execution of a function, all previously constructed automatic objects are destructed before the exception is propagated to the caller. This process is called stack unwinding. During stack unwinding, no further exceptions may leave the destructors of the aforementioned previously constructed automatic objects. Otherwise, the function std::terminate
is called.
This leads to one of the most important guidelines in C++:
Destructors should never throw.
non-local static objects
Static objects defined at namespace scope (commonly referred to as "global variables") and static data members are destructed, in reverse order of their definition, after the execution of main
:
struct X
{
static Foo x; // this is only a *declaration*, not a *definition*
};
Foo a;
Foo b;
int main()
{
} <--- y, x, b and a are destructed here
Foo X::x; // this is the respective definition
Foo y;
Note that the relative order of construction (and destruction) of static objects defined in different translation units is undefined.
If an exception leaves the destructor of a static object, the function std::terminate
is called.
local static objects
Static objects defined inside functions are constructed when (and if) control flow passes through their definition for the first time.1
They are destructed in reverse order after the execution of main
:
Foo& get_some_Foo()
{
static Foo x;
return x;
}
Bar& get_some_Bar()
{
static Bar y;
return y;
}
int main()
{
get_some_Bar().do_something(); // note that get_some_Bar is called *first*
get_some_Foo().do_something();
} <--- x and y are destructed here // hence y is destructed *last*
If an exception leaves the destructor of a static object, the function std::terminate
is called.
1: This is an extremely simplified model. The initialization details of static objects are actually much more complicated.
base class subobjects and member subobjects
When control flow leaves the destructor body of an object, its member subobjects (also known as its "data members") are destructed in reverse order of their definition. After that, its base class subobjects are destructed in reverse order of the base-specifier-list:
class Foo : Bar, Baz
{
Quux x;
Quux y;
public:
~Foo()
{
} <--- y and x are destructed here,
}; followed by the Baz and Bar base class subobjects
If an exception is thrown during the construction of one of Foo
's subobjects, then all its previously constructed subobjects will be destructed before the exception is propagated. The Foo
destructor, on the other hand, will not be executed, since the Foo
object was never fully constructed.
Note that the destructor body is not responsible for destructing the data members themselves. You only need to write a destructor if a data member is a handle to a resource that needs to be released when the object is destructed (such as a file, a socket, a database connection, a mutex, or heap memory).
array elements
Array elements are destructed in descending order. If an exception is thrown during the construction of the n-th element, the elements n-1 to 0 are destructed before the exception is propagated.
temporary objects
A temporary object is constructed when a prvalue expression of class type is evaluated. The most prominent example of a prvalue expression is the call of a function that returns an object by value, such as T operator+(const T&, const T&)
. Under normal circumstances, the temporary object is destructed when the full-expression that lexically contains the prvalue is completely evaluated:
__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here
The above function call some_function(a + " " + b)
is a full-expression because it is not part of a larger expression (instead, it is part of an expression-statement). Hence, all temporary objects that are constructed during the evaluation of the subexpressions will be destructed at the semicolon. There are two such temporary objects: the first is constructed during the first addition, and the second is constructed during the second addition. The second temporary object will be destructed before the first.
If an exception is thrown during the second addition, the first temporary object will be destructed properly before propagating the exception.
If a local reference is initialized with a prvalue expression, the lifetime of the temporary object is extended to the scope of the local reference, so you won't get a dangling reference:
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here
If a prvalue expression of non-class type is evaluated, the result is a value, not a temporary object. However, a temporary object will be constructed if the prvalue is used to initialize a reference:
const int& r = i + j;
Dynamic objects and arrays
In the following section, destroy X means "first destruct X and then release the underlying memory". Similarly, create X means "first allocate enough memory and then construct X there".
dynamic objects
A dynamic object created via p = new Foo
is destroyed via delete p
. If you forget to delete p
, you have a resource leak. You should never attempt to do one of the following, since they all lead to undefined behavior:
- destroy a dynamic object via
delete[]
(note the square brackets),free
or any other means - destroy a dynamic object multiple times
- access a dynamic object after it has been destroyed
If an exception is thrown during the construction of a dynamic object, the underlying memory is released before the exception is propagated. (The destructor will not be executed prior to memory release, because the object was never fully constructed.)
dynamic arrays
A dynamic array created via p = new Foo[n]
is destroyed via delete[] p
(note the square brackets). If you forget to delete[] p
, you have a resource leak. You should never attempt to do one of the following, since they all lead to undefined behavior:
- destroy a dynamic array via
delete
,free
or any other means - destroy a dynamic array multiple times
- access a dynamic array after it has been destroyed
If an exception is thrown during the construction of the n-th element, the elements n-1 to 0 are destructed in descending order, the underlying memory is released, and the exception is propagated.
(You should generally prefer std::vector<Foo>
over Foo*
for dynamic arrays. It makes writing correct and robust code much easier.)
reference-counting smart pointers
A dynamic object managed by several std::shared_ptr<Foo>
objects is destroyed during the destruction of the last std::shared_ptr<Foo>
object involved in sharing that dynamic object.
(You should generally prefer std::shared_ptr<Foo>
over Foo*
for shared objects. It makes writing correct and robust code much easier.)
這篇關(guān)于C++中的對(duì)象銷毀的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!