C++中的对象销毁
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6403055/
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
Object destruction in C++
提问by fredoverflow
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?
C++ 中的对象究竟何时被销毁,这意味着什么?我是否必须手动销毁它们,因为没有垃圾收集器?异常如何发挥作用?
(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 thiswould 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.)
(注意:这是Stack Overflow 的 C++ FAQ 的一个条目。如果你想批评以这种形式提供 FAQ 的想法,那么开始所有这一切的 meta 上的帖子将是这样做的地方。答案该问题在C++ 聊天室中受到监控,FAQ 的想法首先在这里开始,因此您的答案很可能会被提出该想法的人阅读。)
回答by fredoverflow
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 notdestroy 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.
如果在函数执行期间抛出异常,则在将异常传播给调用者之前,所有先前构造的自动对象都会被销毁。这个过程称为堆栈展开。在堆栈展开期间,前面提到的自动对象的析构函数不会有进一步的异常。否则,将std::terminate
调用该函数。
This leads to one of the most important guidelines in C++:
这导致了 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
:
在命名空间范围内定义的静态对象(通常称为“全局变量”)和静态数据成员在执行以下操作后按照其定义的相反顺序被销毁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.
如果异常离开静态对象的析构函数,std::terminate
则调用该函数。
local static objects
本地静态对象
Static objects defined inside functions are constructed when (and if) control flow passes through their definition for the first time.1They are destructed in reverse order after the execution of main
:
在函数内部定义的静态对象是在(以及如果)控制流第一次通过它们的定义时构造的。1它们在执行后以相反的顺序销毁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.
如果异常离开静态对象的析构函数,std::terminate
则调用该函数。
1: This is an extremely simplified model. The initialization details of static objects are actually much more complicated.
1:这是一个极其简化的模型。静态对象的初始化细节其实要复杂得多。
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:
当控制流离开对象的析构函数体时,其成员子对象(也称为其“数据成员”)按照其定义的相反顺序进行析构。之后,它的基类子对象按照 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 constructionof 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 notbe executed, since the Foo
object was never fully constructed.
如果在构造Foo
的子对象之一期间抛出异常,则其先前构造的所有子对象将在异常传播之前被破坏。在Foo
析构函数,而另一方面,将不被执行,因为Foo
对象是没有完全建立。
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 constructionof the n-th element, the elements n-1 to 0 are destructed before the exception is propagated.
数组元素按降序销毁。如果在第n 个元素的构造过程中抛出异常,则在传播异常之前销毁元素 n-1 到 0。
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:
当评估类类型的纯右值表达式时,会构造一个临时对象。prvalue 表达式最突出的例子是调用按值返回对象的函数,例如T operator+(const T&, const T&)
. 在正常情况下,当完全计算词法上包含纯右值的完整表达式时,临时对象会被破坏:
__________________________ 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.
上面的函数调用some_function(a + " " + b)
是一个完整的表达式,因为它不是更大表达式的一部分(相反,它是表达式语句的一部分)。因此,在评估子表达式期间构造的所有临时对象都将在分号处销毁。有两个这样的临时对象:第一个是在第一次加法时构造的,第二个是在第二次加法时构造的。第二个临时对象将在第一个之前销毁。
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 willbe constructed if the prvalue is used to initialize a reference:
如果计算非类类型的纯右值表达式,则结果是值,而不是临时对象。但是,如果使用纯右值来初始化引用,则会构造一个临时对象:
const int& r = i + j;
Dynamic objects and arrays
动态对象和数组
In the following section, destroy Xmeans "first destruct X and then release the underlying memory". Similarly, create Xmeans "first allocate enough memory and then construct X there".
在下一节中,destroy X 的意思是“先销毁 X,然后释放底层内存”。同样,create X 的意思是“先分配足够的内存,然后在那里构造 X”。
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:
通过创建的动态对象通过p = new Foo
销毁delete p
。如果你忘记了delete p
,你就会有资源泄漏。您永远不应该尝试执行以下操作之一,因为它们都会导致未定义的行为:
- 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
- 通过
delete[]
(注意方括号)free
或任何其他方式销毁动态对象 - 多次销毁动态对象
- 在动态对象被销毁后访问它
If an exception is thrown during the constructionof a dynamic object, the underlying memory is released before the exception is propagated. (The destructor will notbe 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:
通过创建的动态数组通过p = new Foo[n]
销毁delete[] p
(注意方括号)。如果你忘记了delete[] p
,你就会有资源泄漏。您永远不应该尝试执行以下操作之一,因为它们都会导致未定义的行为:
- 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
- 破坏通过动态数组
delete
,free
或任何其他方式 - 多次销毁动态数组
- 在动态数组被销毁后访问它
If an exception is thrown during the constructionof 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.
如果在第n 个元素的构造过程中抛出异常,则将第 n-1 到 0 个元素按降序销毁,释放底层内存,并传播异常。
(You should generally prefer std::vector<Foo>
over Foo*
for dynamic arrays. It makes writing correct and robust code much easier.)
(通常你应该更喜欢std::vector<Foo>
在Foo*
动态数组,这使得编写正确的,健壮的代码要容易得多。)
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.
由多个对象管理的动态对象在共享该动态对象所涉及std::shared_ptr<Foo>
的最后一个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.)
(通常你应该更喜欢std::shared_ptr<Foo>
在Foo*
共享对象。这使得更容易编写正确的和可靠的代码。)
回答by Martin York
The destructor of an object is called automatically when the object lifespan ends and it is destroyed. You should not usually call it manually.
当对象生命周期结束并被销毁时,会自动调用对象的析构函数。您通常不应该手动调用它。
We will use this object as an example:
我们将以这个对象为例:
class Test
{
public:
Test() { std::cout << "Created " << this << "\n";}
~Test() { std::cout << "Destroyed " << this << "\n";}
Test(Test const& rhs) { std::cout << "Copied " << this << "\n";}
Test& operator=(Test const& rhs) { std::cout << "Assigned " << this << "\n";}
};
There are three (four in C++11) distinct types of object in C++ and the type of the object defines the objects lifespan.
C++ 中有三种(C++11 中有四种)不同类型的对象,对象的类型定义了对象的生命周期。
- Static Storage duration objects
- Automatic Storage duration objects
- Dynamic Storage duration objects
- (In C++11) Thread Storage duration objects
- 静态存储持续时间对象
- 自动存储持续时间对象
- 动态存储持续时间对象
- (在 C++11 中)线程存储持续时间对象
Static Storage duration objects
静态存储持续时间对象
These are the simplest and equate to global variables. The lifespan of these objects is (usually) the length of the application. These are (usually) constructed before main is entered and destroyed (in the reverse order of being created) after we exit main.
这些是最简单的,等同于全局变量。这些对象的生命周期(通常)是应用程序的长度。这些(通常)是在进入 main 之前构造的,并在我们退出 main 之后销毁(以与创建相反的顺序)。
Test global;
int main()
{
std::cout << "Main\n";
}
> ./a.out
Created 0x10fbb80b0
Main
Destroyed 0x10fbb80b0
Note 1: There are two other type of static storage duration object.
注 1:还有两种其他类型的静态存储持续时间对象。
static member variables of a class.
类的静态成员变量。
These are for all sense and purpose the same as global variables in terms of lifespan.
就生命周期而言,这些在所有意义上和目的都与全局变量相同。
static variables inside a function.
函数内的静态变量。
These are lazily created static storage duration objects. They are created on first use (in a thread safe manor for C++11). Just like other static storage duration objects they are destroyed when the application ends.
这些是延迟创建的静态存储持续时间对象。它们是在第一次使用时创建的(在 C++11 的线程安全庄园中)。就像其他静态存储持续时间对象一样,它们会在应用程序结束时被销毁。
Order of construction/destruction
建造/销毁顺序
- The order of construction within a compilation unit is well defined and the same as declaration.
- The order of construction between compilation units is undefined.
- The order of destruction is the exact inverse of the order of construction.
- 编译单元内的构造顺序是明确定义的,与声明相同。
- 编译单元之间的构造顺序未定义。
- 破坏的顺序与构造的顺序正好相反。
Automatic Storage duration objects
自动存储持续时间对象
These are the most common type of objects and what you should be using 99% of the time.
这些是最常见的对象类型,并且您应该在 99% 的时间使用它们。
These are three main types of automatic variables:
这些是三种主要类型的自动变量:
- local variables inside a function/block
- member variables inside a class/array.
- temporary variables.
- 函数/块内的局部变量
- 类/数组中的成员变量。
- 临时变量。
Local Variables
局部变量
When a function/block is exited all variables declared inside that function/block will be destroyed (in the reverse order of creation).
当一个函数/块退出时,在该函数/块中声明的所有变量都将被销毁(以与创建相反的顺序)。
int main()
{
std::cout << "Main() START\n";
Test scope1;
Test scope2;
std::cout << "Main Variables Created\n";
{
std::cout << "\nblock 1 Entered\n";
Test blockScope;
std::cout << "block 1 about to leave\n";
} // blockScope is destrpyed here
{
std::cout << "\nblock 2 Entered\n";
Test blockScope;
std::cout << "block 2 about to leave\n";
} // blockScope is destrpyed here
std::cout << "\nMain() END\n";
}// All variables from main destroyed here.
> ./a.out
Main() START
Created 0x7fff6488d938
Created 0x7fff6488d930
Main Variables Created
block 1 Entered
Created 0x7fff6488d928
block 1 about to leave
Destroyed 0x7fff6488d928
block 2 Entered
Created 0x7fff6488d918
block 2 about to leave
Destroyed 0x7fff6488d918
Main() END
Destroyed 0x7fff6488d930
Destroyed 0x7fff6488d938
member variables
成员变量
The lifespan of a member variables is bound to the object that owns it. When an owners lifespan ends all its members lifespan also ends. So you need to look at the lifetime of an owner which obeys the same rules.
成员变量的生命周期与拥有它的对象绑定。当所有者的生命周期结束时,其所有成员的生命周期也结束。因此,您需要查看遵守相同规则的所有者的生命周期。
Note: Members are always destroyed before the owner in reverse order of creation.
注意:成员总是在所有者之前以创建的相反顺序被销毁。
- Thus for class members they are created in the order of declaration
and destroyed in the reverse order of declaration - Thus for array members they are created in order 0-->top
and destroyed in the reverse order top-->0
- 因此,对于类成员,它们按照声明的顺序创建,并按照声明
的相反顺序销毁 - 因此,对于数组成员,它们以 0-->top 的顺序创建,
并以相反的顺序 top-->0 销毁
temporary variables
临时变量
These are objects that are created as the result of an expression but are not assigned to a variable. Temporary variables are destroyed just like other automatic variables. It is just that the end of their scope is the end of the statementin which they are created (this is usally the ';').
这些对象是作为表达式的结果创建的,但未分配给变量。临时变量就像其他自动变量一样被销毁。只是它们作用域的结尾是创建它们的语句的结尾(这通常是“;”)。
std::string data("Text.");
std::cout << (data + 1); // Here we create a temporary object.
// Which is a std::string with '1' added to "Text."
// This object is streamed to the output
// Once the statement has finished it is destroyed.
// So the temporary no longer exists after the ';'
Note: There are situations where the life of a temporary can be extended.
But this is not relevant to this simple discussion. By the time you understand that this document will be second nature to you and before it is extending the life of a temporary is not something you want to do.
注意:在某些情况下,临时工的使用寿命可以延长。
但这与这个简单的讨论无关。当您了解这份文件将成为您的第二天性,并且在它延长临时文件的生命之前,您并不是想要做的事情。
Dynamic Storage duration objects
动态存储持续时间对象
These objects have a dynamic lifespan and are created with new
and destroyed with a call to delete
.
这些对象具有动态生命周期new
,通过调用 来创建和销毁delete
。
int main()
{
std::cout << "Main()\n";
Test* ptr = new Test();
delete ptr;
std::cout << "Main Done\n";
}
> ./a.out
Main()
Created 0x1083008e0
Destroyed 0x1083008e0
Main Done
For devs that come from garbage collected languages this can seem strange (managing the lifespan of your object). But the problem is not as bad as it seems. It is unusual in C++ to use dynamically allocated objects directly. We have management objects to control their lifespan.
对于使用垃圾收集语言的开发人员来说,这看起来很奇怪(管理对象的生命周期)。但问题并不像看起来那么糟糕。在 C++ 中直接使用动态分配的对象是不常见的。我们有管理对象来控制它们的寿命。
The closest thing to most other GC collected languages is the std::shared_ptr
. This will keep track of the number of users of a dynamically created object and when all of them are gone will call delete
automatically (I think of this as a better version of a normal Java object).
与大多数其他 GC 收集语言最接近的是std::shared_ptr
. 这将跟踪动态创建的对象的用户数量,当所有用户都消失时将delete
自动调用(我认为这是普通 Java 对象的更好版本)。
int main()
{
std::cout << "Main Start\n";
std::shared_ptr<Test> smartPtr(new Test());
std::cout << "Main End\n";
} // smartPtr goes out of scope here.
// As there are no other copies it will automatically call delete on the object
// it is holding.
> ./a.out
Main Start
Created 0x1083008e0
Main Ended
Destroyed 0x1083008e0
Thread Storage duration objects
线程存储持续时间对象
These are new to the language. They are very much like static storage duration objects. But rather than living the same life as the application they live as long as the thread of execution they are associated with.
这些对语言来说是新的。它们非常像静态存储持续时间对象。但是,它们与应用程序的生命周期不同,它们的生命周期与它们关联的执行线程一样长。