fre*_*low 67 c++ destructor exception c++-faq object-lifetime
什么时候C++中的对象被破坏了,这意味着什么?我是否必须手动销毁它们,因为没有垃圾收集器?例外是如何发挥作用的?
(注意:这是Stack Overflow的C++常见问题解答的一个条目.如果你想批评在这种形式下提供常见问题解答的想法,那么发布所有这些的元数据的发布将是这样做的地方.这个问题在C++聊天室中受到监控,其中FAQ的想法一开始就出现了,所以你的答案很可能被那些提出想法的人阅读.)
fre*_*low 84
在下面的文本中,我将区分范围对象,其破坏时间由其封闭范围(函数,块,类,表达式)和动态对象静态确定,其确切的销毁时间通常直到运行时才知道.
虽然类对象的破坏语义是由析构函数决定的,但标量对象的破坏始终是无操作的.具体地说,破坏指针变量并没有破坏指针对象.
当控制流离开其定义范围时,自动对象(通常称为"局部变量")按其定义的相反顺序被破坏:
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
Run Code Online (Sandbox Code Playgroud)
如果在执行函数期间抛出异常,则在异常传播给调用者之前,所有先前构造的自动对象都将被销毁.此过程称为堆栈展开.在堆栈展开期间,没有进一步的例外可能留下前述构造的自动对象的析构函数.否则,std::terminate
调用该函数.
这导致了C++中最重要的指导原则之一:
破坏者不应该抛弃.
在执行以下命令后,在命名空间范围内定义的静态对象(通常称为"全局变量")和静态数据成员按其定义的相反顺序进行破坏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;
Run Code Online (Sandbox Code Playgroud)
请注意,在不同的转换单元中定义的静态对象的构造(和销毁)的相对顺序是不确定的.
如果异常离开静态对象的析构函数,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*
Run Code Online (Sandbox Code Playgroud)
如果异常离开静态对象的析构函数,std::terminate
则调用该函数.
1:这是一个极其简化的模型.静态对象的初始化细节实际上要复杂得多.
当控制流离开对象的析构函数体时,其成员子对象(也称为"数据成员")将按其定义的相反顺序进行破坏.之后,它的基类子对象以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
Run Code Online (Sandbox Code Playgroud)
如果在构造其中一个Foo
子对象期间抛出异常,则在传播异常之前将破坏其先前构建的所有子对象.在Foo
析构函数,而另一方面,将不被执行,因为Foo
对象是没有完全建立.
请注意,析构函数主体不负责破坏数据成员本身.如果数据成员是在对象被破坏时需要释放的资源的句柄(例如文件,套接字,数据库连接,互斥或堆内存),则只需要编写析构函数.
数组元素按降序销毁.如果在构造第n个元素期间抛出异常,则在传播异常之前破坏元素n-1到0.
在计算类类型的prvalue表达式时,将构造临时对象.prvalue表达式最突出的例子是调用一个按值返回对象的函数,例如T operator+(const T&, const T&)
.在正常情况下,当完全评估词法包含prvalue的完整表达式时,将破坏临时对象:
__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here
Run Code Online (Sandbox Code Playgroud)
上面的函数调用some_function(a + " " + b)
是一个完整表达式,因为它不是更大表达式的一部分(相反,它是表达式语句的一部分).因此,在子表达式的评估期间构造的所有临时对象将在分号处被破坏.有两个这样的临时对象:第一个是在第一次添加期间构建的,第二个是在第二次添加期间构建的.第二个临时对象将在第一个临时对象之前被破坏.
如果在第二次添加期间抛出异常,则在传播异常之前将正确销毁第一个临时对象.
如果使用prvalue表达式初始化本地引用,则临时对象的生命周期将扩展到本地引用的范围,因此您不会获得悬空引用:
{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here
Run Code Online (Sandbox Code Playgroud)
如果计算非类类型的prvalue表达式,则结果是值,而不是临时对象.但是,如果使用prvalue初始化引用,则将构造临时对象:
const int& r = i + j;
Run Code Online (Sandbox Code Playgroud)
在下一节中,destroy X表示"首先破坏X然后释放底层内存".类似地,创建X意味着"首先分配足够的内存,然后在那里构造X".
通过创建的动态对象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*
共享对象,这使得编写正确的,健壮的代码要容易得多.)
Mar*_*ork 35
当对象生命周期结束并被销毁时,会自动调用对象的析构函数.您通常不应手动调用它.
我们将使用此对象作为示例:
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";}
};
Run Code Online (Sandbox Code Playgroud)
C++中有三种(C++ 11中有四种)不同类型的对象,对象的类型定义了对象的生命周期.
这些是最简单的,等同于全局变量.这些对象的生命周期(通常)是应用程序的长度.这些(通常)是在我们退出main之后在main输入和销毁之前(以创建的相反顺序)构造的.
Test global;
int main()
{
std::cout << "Main\n";
}
> ./a.out
Created 0x10fbb80b0
Main
Destroyed 0x10fbb80b0
Run Code Online (Sandbox Code Playgroud)
注1:还有另外两种类型的静态存储持续时间对象.
就生命周期而言,这些意义和目的与全局变量相同.
这些是懒惰创建的静态存储持续时间对象.它们是在第一次使用时创建的(在C++ 11的线程安全庄园中).与其他静态存储持续时间对象一样,它们在应用程序结束时被销毁.
这些是最常见的对象类型,99%的情况下你应该使用它们.
这些是三种主要类型的自动变量:
当退出函数/块时,将破坏在该函数/块内声明的所有变量(以创建的相反顺序).
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
Run Code Online (Sandbox Code Playgroud)
成员变量的生命周期绑定到拥有它的对象.当业主的寿命结束时,其所有成员的寿命也将结束.因此,您需要查看遵守相同规则的所有者的生命周期.
注意:成员始终以所有者的相反顺序销毁.
这些是作为表达式结果创建但未分配给变量的对象.临时变量就像其他自动变量一样被销毁.只是它们的范围的结尾是它们被创建的语句的结尾(这通常是';').
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 ';'
Run Code Online (Sandbox Code Playgroud)
注意:有些情况下可以延长临时寿命.
但这与这个简单的讨论无关.当你明白这个文件对你来说是第二天性之前,在延长临时生命之前你不想做什么.
这些对象具有动态生命周期,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
Run Code Online (Sandbox Code Playgroud)
对于来自垃圾收集语言的开发人员来说,这看起来很奇怪(管理对象的生命周期).但问题并不像看起来那么糟糕.在C++中直接使用动态分配的对象是不常见的.我们有管理对象来控制他们的生命周期.
与大多数其他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
Run Code Online (Sandbox Code Playgroud)
这些是该语言的新功能.它们非常类似于静态存储持续时间对象.但是,与他们所生活的应用程序生活相同的生命,只要与它们相关联的执行线程.