Object destruction in C++
什么时候对象在C++中被破坏,这意味着什么?因为没有垃圾收集器,我必须手动销毁它们吗?例外是如何发挥作用的?
在下面的文本中,我将区分作用域对象(其销毁时间由其封闭作用域(函数、块、类、表达式)静态确定)和动态对象(其确切销毁时间通常在运行时才知道)。好的。
虽然类对象的破坏语义由析构函数确定,但标量对象的破坏始终是不可操作的。具体来说,破坏指针变量不会破坏指针。好的。作用域对象自动对象
当控制流离开其定义的范围时,自动对象(通常称为"局部变量")按其定义的相反顺序被销毁:好的。
1 2 3 4 5 6 7 8 9 10 | 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 |
如果在函数执行期间引发异常,则在将异常传播给调用方之前,将销毁所有先前构造的自动对象。此过程称为堆栈展开。在堆栈展开期间,不会有其他异常离开前面构造的自动对象的析构函数。否则,调用函数
这导致了C++中最重要的指导原则之一:好的。
Destructors should never throw.
Ok.
非本地静态对象
在执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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; |
请注意,在不同的翻译单元中定义的静态对象的相对构造(和销毁)顺序是未定义的。好的。
如果异常离开静态对象的析构函数,则调用函数
在函数内部定义的静态对象是在(和if)控制流首次通过其定义时构造的。1执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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* |
如果异常离开静态对象的析构函数,则调用函数
1:这是一个非常简化的模型。静态对象的初始化细节实际上要复杂得多。好的。基类子对象和成员子对象 当控制流离开对象的析构函数体时,其成员子对象(也称为其"数据成员")将按其定义的相反顺序进行析构函数化。之后,它的基类子对象将按与基本说明符列表相反的顺序进行销毁:好的。 如果在 请注意,析构函数体不负责销毁数据成员本身。如果数据成员是一个资源的句柄,而该资源在对象被破坏时需要释放(例如文件、套接字、数据库连接、互斥体或堆内存),则只需编写一个析构函数。好的。数组元素 数组元素按降序销毁。如果在构造第n个元素的过程中引发异常,那么在传播异常之前,元素n-1到0将被销毁。好的。临时对象 当计算类类型的prvalue表达式时,将构造临时对象。prvalue表达式最突出的例子是调用按值返回对象的函数,例如 上述函数调用 如果在第二次添加期间引发异常,则在传播该异常之前,第一个临时对象将被正确销毁。好的。 如果用prvalue表达式初始化了本地引用,则临时对象的生存期将扩展到本地引用的范围,因此不会得到悬空引用:好的。 如果计算非类类型的prvalue表达式,则结果是一个值,而不是临时对象。但是,如果使用prvalue初始化引用,则将构造一个临时对象:好的。 动态对象和数组 在下面的部分中,destroy x表示"首先销毁x,然后释放底层内存"。同样,create x的意思是"首先分配足够的内存,然后在那里构造x"。好的。动态对象 通过 如果在动态对象的构造过程中引发异常,则在传播异常之前释放底层内存。(在释放内存之前不会执行析构函数,因为对象从未完全构造过。)好的。动态数组 通过 如果在构造第n个元素的过程中引发异常,那么元素n-1到0将按降序销毁,释放底层内存,并传播异常。好的。 (对于动态数组,您通常更喜欢 由多个 (对于共享对象,您通常更喜欢 对象的析构函数在对象寿命结束并被销毁时自动调用。您通常不应该手动调用它。好的。 我们将以这个对象为例:好的。 C++中有三个(C++中有四个)不同类型的对象,对象的类型定义对象的寿命。好的。 静态存储持续时间对象 这些是最简单的,等于全局变量。这些对象的寿命(通常)是应用程序的长度。这些通常是在进入主系统之前构建的,在退出主系统之后销毁(按照创建的相反顺序)。好的。 注1:静态存储持续时间对象还有两种类型。好的。类的静态成员变量。 这些在所有意义和目的上都与寿命方面的全局变量相同。好的。函数内部的静态变量。 这些是延迟创建的静态存储持续时间对象。它们是在第一次使用时创建的(在C++ 11的线程安全庄园中)。与其他静态存储持续时间对象一样,它们在应用程序结束时被销毁。好的。施工/破坏顺序 自动存储持续时间对象 这些是最常见的对象类型,您应该使用99%的时间。好的。 这是三种主要的自动变量:好的。 局部变量 当函数/块退出时,该函数/块中声明的所有变量都将被销毁(按创建的相反顺序)。好的。 成员变量 成员变量的寿命绑定到拥有它的对象。当所有者的寿命结束时,其所有成员的寿命也将结束。所以你需要看看一个遵守相同规则的拥有者的一生。好的。 注意:成员总是在所有者之前按与创建相反的顺序被销毁。好的。 临时变量 这些对象是作为表达式的结果创建的,但不分配给变量。临时变量和其他自动变量一样被销毁。只是它们作用域的结尾是在其中创建它们的语句的结尾(这通常是";")。好的。 注意:有些情况下,临时工的寿命可以延长。但这与这个简单的讨论无关。当你明白这份文件将是你的第二天性,在它延长一个临时的生命之前,你不想做什么。好的。动态存储持续时间对象 这些对象具有动态寿命,使用 对于来自垃圾收集语言的开发人员来说,这看起来很奇怪(管理对象的寿命)。但问题并没有看上去那么严重。在C++中,直接使用动态分配的对象是不寻常的。我们有管理对象来控制它们的寿命。好的。 与大多数其他GC收集的语言最接近的是 线程存储持续时间对象 这些是新的语言。它们非常类似于静态存储持续时间对象。但与其与应用程序生活在同一个生命周期中,不如与它们关联的执行线程生活在同一个生命周期中。好的。好啊。
2
3
4
5
6
7
8
9
10
11
{
Quux x;
Quux y;
public:
~Foo()
{
} <--- y and x are destructed here,
}; followed by the Baz and Bar base class subobjects
2
3
4
5
___________ subexpression
_______ subexpression
some_function(a +"" + b);
^ both temporary objects are destructed here
2
3
4
5
const Foo& r = a +"" + b;
^ first temporary (a +"") is destructed here
// ...
} <--- second temporary (a +"" + b) is destructed not until here
2
3
4
5
6
7
8
9
10
11
12
{
public:
Test() { std::cout <<"Created " << this <<"
";}
~Test() { std::cout <<"Destroyed " << this <<"
";}
Test(Test const& rhs) { std::cout <<"Copied " << this <<"
";}
Test& operator=(Test const& rhs) { std::cout <<"Assigned " << this <<"
";}
};
2
3
4
5
6
7
8
9
10
11
int main()
{
std::cout <<"Main
";
}
> ./a.out
Created 0x10fbb80b0
Main
Destroyed 0x10fbb80b0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
std::cout <<"Main() START
";
Test scope1;
Test scope2;
std::cout <<"Main Variables Created
";
{
std::cout <<"
block 1 Entered
";
Test blockScope;
std::cout <<"block 1 about to leave
";
} // blockScope is destrpyed here
{
std::cout <<"
block 2 Entered
";
Test blockScope;
std::cout <<"block 2 about to leave
";
} // blockScope is destrpyed here
std::cout <<"
Main() END
";
}// 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
2
3
4
5
6
7
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 ';'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
std::cout <<"Main()
";
Test* ptr = new Test();
delete ptr;
std::cout <<"Main Done
";
}
> ./a.out
Main()
Created 0x1083008e0
Destroyed 0x1083008e0
Main Done
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
std::cout <<"Main Start
";
std::shared_ptr<Test> smartPtr(new Test());
std::cout <<"Main End
";
} // 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