问题描述
C++ 中的对象究竟是什么时候销毁的,这意味着什么?由于没有垃圾收集器,我是否必须手动销毁它们?异常如何发挥作用?
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?
<子>(注意:这是 Stack Overflow 的 C++ 常见问题解答的一个条目.如果你想批评以这种形式提供一个常见问题解答,然后 开始发布在 meta 上所有这些都是这样做的地方.该问题的答案在 C++ 聊天室,FAQ 想法最初是从这里开始的,所以你的答案很可能会被提出这个想法的人阅读.)
(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.
当控制流离开其定义范围时,自动对象(通常称为局部变量")会按照其定义的相反顺序被破坏:
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
如果在函数执行过程中抛出异常,所有之前构造的自动对象都会在异常传播到调用者之前被销毁.这个过程称为堆栈展开.在堆栈展开期间,不会有进一步的异常离开上述先前构造的自动对象的析构函数.否则,函数 std::terminate
被调用.
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.
这导致了 C++ 中最重要的准则之一:
This leads to one of the most important guidelines in C++:
析构函数永远不应该抛出.
Destructors should never throw.
非本地静态对象
在命名空间范围内定义的静态对象(通常称为全局变量")和静态数据成员在执行 main
后按其定义的相反顺序被销毁:
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.
如果异常离开静态对象的析构函数,则调用函数std::terminate
.
If an exception leaves the destructor of a static object, the function std::terminate
is called.
在函数内部定义的静态对象在(如果)控制流第一次通过它们的定义时被构造.1在main
执行后,它们以相反的顺序被销毁:
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*
如果异常离开静态对象的析构函数,则调用函数std::terminate
.
If an exception leaves the destructor of a static object, the function std::terminate
is called.
1:这是一个极其简化的模型.静态对象的初始化细节其实要复杂得多.
当控制流离开一个对象的析构函数体时,它的成员子对象(也称为它的数据成员")按照它们定义的相反顺序被销毁.之后,它的基类子对象会按照 base-specifier-list 的相反顺序被销毁:
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
如果在Foo
的其中一个子对象的构造 过程中抛出异常,则其先前构造的所有子对象都将在异常传播之前被破坏.另一方面,Foo
析构函数将 被执行,因为 Foo
对象从未完全构造.
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).
数组元素按降序销毁.如果在第n个元素的构造过程中抛出异常,则在传播异常之前将元素n-1到0销毁.
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.
在评估类类型的纯右值表达式时会构造一个临时对象.prvalue 表达式最突出的示例是调用按值返回对象的函数,例如 T operator+(const T&, const T&)
.在正常情况下,当词法上包含纯右值的完整表达式被完全求值时,临时对象就会被破坏:
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
上述函数调用 some_function(a + " " + b)
是一个完整表达式,因为它不是更大表达式的一部分(相反,它是表达式语句的一部分).因此,在评估子表达式期间构造的所有临时对象都将在分号处被破坏.有两个这样的临时对象:第一个是在第一次添加期间构造的,第二个是在第二次添加期间构造的.第二个临时对象将在第一个之前被销毁.
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;
动态对象和数组
在下一节中,destroy X的意思是先破坏X,然后释放底层内存".同理,create X的意思是先分配足够的内存,然后再在那里构造X".
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".
通过p = new Foo
创建的动态对象通过delete p
销毁.如果你忘记delete p
,你就有了资源泄露.您永远不应尝试执行以下操作之一,因为它们都会导致未定义的行为:
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:
- 通过
delete[]
(注意方括号)、free
或任何其他方式销毁动态对象 - 多次销毁动态对象
- 在动态对象被销毁后访问它
- 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.)
通过 p = new Foo[n]
创建的动态数组通过 delete[] p
销毁(注意方括号).如果您忘记 delete[] p
,则您有资源泄漏.您永远不应尝试执行以下操作之一,因为它们都会导致未定义的行为:
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:
- 通过
delete
、free
或任何其他方式销毁动态数组 - 多次销毁动态数组
- 在动态数组被销毁后访问它
- 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
如果在第n个元素的构造过程中抛出异常,则按降序销毁元素n-1到0,释放底层内存,传播异常.
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.
(对于动态数组,您通常应该更喜欢 std::vector<Foo>
而不是 Foo*
.它使编写正确且健壮的代码变得更加容易.)
(You should generally prefer std::vector<Foo>
over Foo*
for dynamic arrays. It makes writing correct and robust code much easier.)
一个由多个 std::shared_ptr
对象管理的动态对象在销毁参与共享的最后一个 std::shared_ptr
对象时被销毁那个动态对象.
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.
(对于共享对象,您通常应该更喜欢 std::shared_ptr
而不是 Foo*
.它使编写正确且健壮的代码变得更加容易.)
(You should generally prefer std::shared_ptr<Foo>
over Foo*
for shared objects. It makes writing correct and robust code much easier.)
这篇关于C++ 中的对象销毁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!