在 main 执行后,在命名空间范围内定义的静态对象(通常称为"global variables")和静态数据成员按其定义的相反顺序进行破坏:
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;
请注意,在不同的转换单元中定义的静态对象的构造(和销毁)的相对顺序是不确定的 .
如果异常离开静态对象的析构函数,则调用函数 std::terminate .
本地静态对象
函数内部定义的静态对象是在控制流第一次通过它们的定义时(和如果)构造的.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*
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
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 ';'
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
2 回答
在下面的文本中,我将区分范围对象,其破坏时间由其封闭范围(函数,块,类,表达式)和动态对象静态确定,其确切的销毁时间通常直到运行时才知道 .
虽然类对象的破坏语义是由析构函数决定的,但标量对象的破坏始终是无操作的 . 具体来说,破坏指针变量不会破坏指针 .
Scoped对象
自动对象
当控制流离开其定义范围时,自动对象(通常称为“局部变量”)按其定义的相反顺序被破坏:
如果在执行函数期间抛出异常,则在异常传播给调用者之前,所有先前构造的自动对象都将被销毁 . 此过程称为堆栈展开 . 在堆栈展开期间,没有进一步的例外可能留下前述构造的自动对象的析构函数 . 否则,调用函数
std::terminate
.这导致了C中最重要的指导原则之一:
非本地静态对象
在
main
执行后,在命名空间范围内定义的静态对象(通常称为"global variables")和静态数据成员按其定义的相反顺序进行破坏:请注意,在不同的转换单元中定义的静态对象的构造(和销毁)的相对顺序是不确定的 .
如果异常离开静态对象的析构函数,则调用函数
std::terminate
.本地静态对象
函数内部定义的静态对象是在控制流第一次通过它们的定义时(和如果)构造的.1在
main
执行后它们以相反的顺序被破坏:如果异常离开静态对象的析构函数,则调用函数
std::terminate
.1:这是一个极其简化的模型 . 静态对象的初始化细节实际上要复杂得多 .
基类子对象和成员子对象
当控制流离开对象的析构函数体时,其成员子对象(也称为“数据成员”)将按其定义的相反顺序进行破坏 . 之后,它的基类子对象以base-specifier-list的相反顺序被破坏:
如果在构造
Foo
的一个子对象期间抛出异常,则在传播异常之前将破坏其先前构建的所有子对象 . 另一方面,Foo
析构函数不会被执行,因为Foo
对象从未完全构造 .请注意,析构函数主体不负责破坏数据成员本身 . 如果数据成员是在对象被破坏时需要释放的资源的句柄(例如文件,套接字,数据库连接,互斥或堆内存),则只需要编写析构函数 .
数组元素
数组元素按降序销毁 . 如果在构造第n个元素期间抛出异常,则在传播异常之前破坏元素n-1到0 .
临时对象
在计算类类型的prvalue表达式时,将构造临时对象 . prvalue表达式最突出的例子是调用一个按值返回对象的函数,例如
T operator+(const T&, const T&)
. 在正常情况下,当完全评估词法包含prvalue的完整表达式时,将破坏临时对象:上面的函数调用
some_function(a + " " + b)
是一个完整表达式,因为它不是更大表达式的一部分(相反,它是一个表达式的一部分)表达式语句) . 因此,在子表达式的评估期间构造的所有临时对象将在分号处被破坏 . 有两个这样的临时对象:第一个是在第一次添加期间构建的,第二个是在第二次添加期间构建的 . 第二个临时对象将在第一个临时对象之前被破坏 .如果在第二次添加期间抛出异常,则在传播异常之前将正确销毁第一个临时对象 .
如果使用prvalue表达式初始化本地引用,则临时对象的生命周期将扩展到本地引用的范围,因此您不会获得悬空引用:
如果计算非类类型的prvalue表达式,则结果是值,而不是临时对象 . 但是,如果使用prvalue初始化引用,则将构造临时对象:
动态对象和数组
在以下部分中,销毁X表示"first destruct X and then release the underlying memory" . 同样,创建X表示"first allocate enough memory and then construct X there" .
动态对象
通过
p = new Foo
创建的动态对象通过delete p
销毁 . 如果忘记delete p
,则表示资源泄漏 . 您永远不应尝试执行以下操作之一,因为它们都会导致未定义的行为:通过
delete[]
(注意方括号),free
或任何其他方式销毁动态对象多次销毁动态对象
在销毁后访问动态对象
如果在构造动态对象期间抛出异常,则在传播异常之前释放底层内存 . (析构函数不会在内存释放之前执行,因为该对象从未完全构造 . )
动态数组
通过
p = new Foo[n]
创建的动态数组通过delete[] p
销毁(注意方括号) . 如果忘记delete[] p
,则表示资源泄漏 . 您永远不应尝试执行以下操作之一,因为它们都会导致未定义的行为:通过
delete
,free
或任何其他方式销毁动态数组多次销毁动态数组
在销毁后访问动态数组
如果在构造第n个元素期间抛出异常,则元素n-1到0按降序被破坏,底层存储器被释放,并且异常被传播 .
(对于动态数组,您通常更喜欢使用
std::vector<Foo>
而不是Foo*
. 这使得编写正确且健壮的代码变得更加容易 . )引用计数智能指针
在销毁共享该动态对象所涉及的最后一个
std::shared_ptr<Foo>
对象期间,将销毁由多个std::shared_ptr<Foo>
对象管理的动态对象 .(对于共享对象,您通常应该优先使用
std::shared_ptr<Foo>
而不是Foo*
. 这使得编写正确且健壮的代码变得更加容易 . )当对象生命周期结束并被销毁时,会自动调用对象的析构函数 . 您通常不应手动调用它 .
我们将使用此对象作为示例:
C中有三个(C 11中有四个)不同类型的对象,对象的类型定义了对象的生命周期 .
静态存储持续时间对象
自动存储持续时间对象
动态存储持续时间对象
(在C 11中)线程存储持续时间对象
静态存储持续时间对象
这些是最简单的,等同于全局变量 . 这些对象的生命周期(通常)是应用程序的长度 . 这些(通常)是在我们退出main之后在main输入和销毁之前(以创建的相反顺序)构造的 .
注1:还有另外两种类型的静态存储持续时间对象 .
类的静态成员变量 .
就生命周期而言,这些意义和目的与全局变量相同 .
函数内部的静态变量 .
这些是懒惰创建的静态存储持续时间对象 . 它们是在首次使用时创建的(在C 11的线程安全庄园中) . 与其他静态存储持续时间对象一样,它们在应用程序结束时被销毁 .
施工/销毁顺序
编译单元内的构造顺序定义明确,与声明相同 .
编译单元之间的构造顺序未定义 .
破坏的顺序与构造顺序完全相反 .
自动存储持续时间对象
这些是最常见的对象类型,99%的情况下你应该使用它们 .
这些是三种主要类型的自动变量:
函数/块内的局部变量
类/数组中的
成员变量 .
临时变量 .
局部变量
当退出函数/块时,将破坏在该函数/块内声明的所有变量(以创建的相反顺序) .
成员变量
成员变量的生命周期绑定到拥有它的对象 . 当业主的寿命结束时,其所有成员的寿命也将结束 . 因此,您需要查看遵守相同规则的所有者的生命周期 .
注意:成员始终以所有者的相反顺序销毁 .
因此,对于类成员,它们是按声明的顺序创建的
并按照与声明相反的顺序销毁
因此,对于数组成员,它们按0 - >顶部顺序创建
并以相反的顺序销毁 - > 0
临时变量
这些是作为表达式结果创建但未分配给变量的对象 . 临时变量就像其他自动变量一样被销毁 . 只是它们的范围的结尾是创建它们的 statement 的结尾(这通常是';') .
注意:有些情况下可以延长临时寿命 .
但这与这个简单的讨论无关 . 当你明白这个文件对你来说是第二天性之前,在延长临时生命之前你不想做什么 .
动态存储持续时间对象
这些对象具有动态生命周期,并使用
new
创建,并通过调用delete
进行销毁 .对于来自垃圾收集语言的开发人员来说,这看起来很奇怪(管理对象的生命周期) . 但问题并不像看起来那么糟糕 . 在C中直接使用动态分配的对象是不常见的 . 我们有管理对象来控制他们的生命周期 .
与大多数其他GC收集的语言最接近的是
std::shared_ptr
. 这将跟踪动态创建的对象的用户数量,并且当它们全部消失时将自动调用delete
(我认为这是普通Java对象的更好版本) .线程存储持续时间对象
这些是该语言的新功能 . 它们非常类似于静态存储持续时间对象 . 但是,与他们所生活的应用程序生活相同的生命,只要与它们相关联的执行线程 .