C + + 中的对象破坏

在 C + + 中,对象究竟何时被销毁,这意味着什么?既然没有垃圾收集器,我必须手动销毁它们吗?异常是如何发挥作用的?

(注意: 这是 Stack Overflow 的 C + + 常见问题解答的条目。如果你想批判这种形式的 FAQ 提供的想法,那么 引发这一切的超能力者的帖子将是这样做的地方。这个问题的答案在 C++ chatroom中被监控,FAQ 的想法最初就是在 C++ chatroom中出现的,所以你的答案很可能会被那些想出这个想法的人看到。)

38813 次浏览

在下面的文本中,我将区分 有范围的物体dynamic objects,前者的销毁时间由其封闭范围(函数、块、类、表达式)静态决定,后者的确切销毁时间通常要到运行时才知道。

虽然类对象的销毁语义是由销毁函数决定的,但标量对象的销毁总是不可操作的。具体来说,销毁指针变量会使 没有销毁指针。

Scoped 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

如果在函数执行期间引发异常,则在将异常传播到调用方之前,将销毁以前构造的所有自动对象。这个过程称为 堆叠放松堆叠放松。在堆栈展开期间,没有进一步的异常可以离开前面提到的先前构造的自动对象的析构函数。否则,函数 std::terminate将被调用。

这就引出了 C + + 中最重要的指导方针之一:

毁灭者永远不应该抛出。

非局部静态对象非局部静态对象

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;

注意,在不同的翻译单元中定义的静态对象的构造(和破坏)的相对顺序是不确定的。

如果异常离开静态对象的析构函数,则调用函数 std::terminate

局部静态对象局部静态对象

Static objects defined inside functions are constructed when (and if) control flow passes through their definition for the first time.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*

如果异常离开静态对象的析构函数,则调用函数 std::terminate

1: 这是一个非常简化的模型。静态对象的初始化细节实际上要复杂得多。

基类子对象和成员子对象

当控制流离开对象的析构函数体时,它的成员子对象(也称为它的“数据成员”)将按与其定义相反的顺序进行析构。然后,它的基类子对象按照基指定符列表的相反顺序被销毁:

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对象从未被完全构造。

请注意,析构函数体本身不负责析构数据成员。如果一个数据成员是一个资源句柄,那么只需要编写一个析构函数,该句柄在对象被析构时需要释放(例如文件、套接字、数据库连接、互斥或堆内存)。

数组元素

数组元素按降序销毁。如果在 n-th 元素的 建筑工程期间引发异常,则在传播异常之前将销毁元素 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

上面的函数调用 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

如果计算非类类型的 prvalue 表达式,结果是 价值,而不是临时对象。但是,如果 prvalue 用于初始化引用,则构造一个临时对象 威尔:

const int& r = i + j;

Dynamic objects and arrays

In the following section, 摧毁 X means "first destruct X and then release the underlying memory". 类似地,创造 X意味着“首先分配足够的内存,然后在那里构造 X”。

动态物体

通过 p = new Foo创建的动态对象通过 delete p销毁。如果您忘记了 delete p,就会出现资源泄漏。你千万不要尝试做以下任何一件事,因为它们都会导致未定义行为:

  • 通过 delete[](注意方括号)、 free或任何其他方法破坏一个动态对象
  • 多次摧毁一个动态物体
  • 在动态对象被销毁后访问该对象

如果在动态对象的 建筑工程期间引发异常,则在传播异常之前释放基础内存。 (The destructor will 没有 be executed prior to memory release, because the object was never fully constructed.)

动态数组

通过 p = new Foo[n]创建的动态数组通过 delete[] p销毁(注意方括号)。如果您忘记了 delete[] p,就会出现资源泄漏。你千万不要尝试做以下任何一件事,因为它们都会导致未定义行为:

  • 通过 deletefree或任何其他方法破坏动态数组
  • 多次破坏动态数组
  • 在动态数组被销毁后访问它

If an exception is thrown during the 建筑工程 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*,因为它使得编写正确和健壮的代码更加容易。)

引用计数智能指针

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>而不是 Foo*,这样可以更容易地编写正确和健壮的代码。)

对象的析构函数在对象的生命周期结束并被销毁时自动调用。您通常不应该手动调用它。

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";}
};

C + + 中有三种(C + + 11中有四种)不同类型的对象,对象的类型定义了对象的生命周期。

  • Static Storage duration objects
  • 自动存储持续时间对象
  • Dynamic Storage duration objects
  • (在 C + + 11中)线程存储持续时间对象

Static Storage duration objects

这些是最简单的全局变量。这些对象的生命周期(通常)是应用程序的长度。这些(通常)是在我们退出 main 之后在 main 被输入和销毁之前构建的(在创建的相反顺序中)。

Test  global;
int main()
{
std::cout << "Main\n";
}


> ./a.out
Created    0x10fbb80b0
Main
Destroyed  0x10fbb80b0

注意1: 还有另外两种类型的静态存储持续时间对象。

类的静态成员变量。

这些在所有意义和目的上与全局变量在寿命方面是相同的。

函数内的静态变量。

这些是延迟创建的静态存储持续时间对象。它们是在第一次使用时创建的(在 C + + 11的线程安全领地中)。就像其他静态存储持续时间对象一样,它们在应用程序结束时被销毁。

建造/销毁令

  • 编译单元内的结构顺序是明确的,与声明相同。
  • 编译单元之间的结构顺序未定义。
  • 破坏的顺序与建筑的顺序完全相反。

自动存储持续时间对象

这些是最常见的对象类型,以及99% 的时间应该使用的对象。

自动变量主要有三种类型:

  • local variables inside a function/block
  • 类/数组中的成员变量。
  • 临时变量。

局部变量

当一个函数/块退出时,该函数/块内部声明的所有变量都将被销毁(以创建的相反顺序)。

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

成员变量

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.

注意: 成员总是按照创建的相反顺序在所有者之前被销毁。

  • 因此,对于类成员,它们是按声明的顺序创建的
    并按照相反的声明顺序销毁
  • 因此,对于数组成员,它们按0-> top 的顺序创建
    以相反的顺序销毁顶部—— > 0

临时变量

这些对象是作为表达式的结果创建的,但不赋给变量。临时变量和其他自动变量一样被销毁。只是它们作用域的末尾是创建它们的 声明的末尾(通常是‘ ;’)。

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 ';'

注意: 有些情况下可以延长临时工的生命。
但这与这个简单的讨论无关。当您明白这个文档将成为您的第二天性时,并且在它延长临时性文档的生命之前,您不希望这样做。

动态存储持续时间对象

These objects have a dynamic lifespan and are created with new and destroyed with a call to 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.

与大多数其他 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

线程存储持续时间对象

这些是新的语言。它们非常类似于静态存储持续时间对象。但是,它们并没有像应用程序那样生活,而是只要与它们相关联的执行线程存在,它们就存在。