C ++中的对象销毁 [英] Object destruction in C++

查看:172
本文介绍了C ++中的对象销毁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当在C ++中销毁对象时,这是什么意思?我必须手动销毁它们,因为没有垃圾收集器?




(注意:这是一个条目 Stack Overflow的C ++常见问题如果您想批评在此表单中提供常见问题的想法,则在开始所有这一切的元的发布将是这个问题的答案。在 C ++聊天室中进行监控,其中常见问题解答首先开始,因此您的答案是

解决方案

p>在下面的文本中,我将区分破坏时间由其包围范围(函数,块,类,表达式)和动态对象静态确定的范围对象 em>



虽然类对象的销毁语义是由析构函数决定的,但是标量对象的销毁总是无操作。具体来说,破坏指针变量不会销毁指针。



作用域对象



自动对象



当控制流离开其定义的范围时,自动对象(通常称为局部变量)按照其定义的相反顺序被销毁:

  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 ++中最重要的指南之一:


析构函数不应该抛出。




非本地静态对象



在命名空间范围(通常称为全局变量)中定义的静态对象和静态数据成员按其定义的相反顺序被销毁,执行之后

  struct X 
{
static Foo x; // this is only a * declaration *,not a * definition *
};

Foo a;
Foo b;

int main()
{
}< --- y,x,b和a在这里被销毁

Foo X :: x ; //这是相应的定义
Foo y;请注意,在不同翻译单元中定义的静态对象的构造(和销毁)的相对顺序是未定义的(例如,如下):


$ b < 。



如果一个异常离开静态对象的析构函数,函数 std :: terminate / p>

局部静态对象



当(和if)控制流通过其定义 1
在执行 main 后,它们以相反的顺序被破坏:

  Foo& get_some_Foo()
{
static Foo x;
return x;
}

酒吧& get_some_Bar()
{
static Bar y;
return y;
}

int main()
{
get_some_Bar()。do_something(); //注意get_some_Bar被调用* first *
get_some_Foo()。do_something();
}< --- x和y在这里被销毁//因此y被销毁* last *


$ b b

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



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

基类子对象和成员子对象



当控制流离开对象的析构体时,其成员子对象(也称为其数据成员)以其定义的相反顺序被销毁。之后,其基类子对象将以base-specifier-list的相反顺序被破坏:

  class Foo:Bar,Baz 
{
Quux x;
Quux y;

public:

〜Foo()
{
}< --- y and x are destructed here,
};其次是Baz和Bar基类子对象

如果在 / em> Foo 的子对象之一,那么所有之前构造的子对象都将在异常被传播之前被销毁。另一方面, Foo 析构函数将不会执行,因为 Foo 对象从未完全构建。



请注意,析构函数体不负责破坏数据成员本身。如果数据成员是当对象被销毁时需要释放的资源(例如文件,套接字,数据库连接,互斥体或堆内存)的句柄,则只需编写析构函数。 p>

数组元素



数组元素按降序排列。如果在第n个元素的构造期间抛出异常,则在异常被传播之前元素n-1到0被破坏。



< h2>临时对象

当类型类型的prvalue表达式被求值时,构造一个临时对象。 prvalue表达式最突出的例子是调用按值返回对象的函数,例如 T operator +(const T& const T&)。在正常情况下,当完全计算包含prvalue的完整表达式时,临时对象被销毁:

 表达式
___________子表达式
_______子表达式
some_function(a ++ b);
^这两个临时对象都在这里被销毁

上述函数调用 some_function(a ++ b)是一个完整表达式,因为它不是较大表达式的一部分(而是表达式语句的一部分)。因此,在子表达式的求值期间构造的所有临时对象将在分号处被破坏。有两个这样的临时对象:第一个在第一次添加期间构建,第二个在第二次添加期间构建。第二个临时对象将在第一个临时对象之前被销毁。



如果在第二次添加期间抛出异常,则第一个临时对象在传播异常之前将被正确地销毁。 / p>

如果使用prvalue表达式初始化本地引用,则临时对象的生命周期将扩展到本地引用的作用域,因此您将无法获得悬挂引用:

  {
const Foo& r = a ++ b;
^第一临时(a +)在这里被销毁
// ...
} <---第二临时(a ++ b)

如果计算非类型的prvalue表达式,结果为 / em>,而不是临时对象。但是,如果prvalue用于初始化引用,则将构造临时对象

  const int& r = i + j; 



动态对象和数组



下面的部分, destroy X 意味着先销毁X然后释放底层内存。
类似地, create X 表示先分配足够的内存,然后在那里构造X.



动态对象



通过 p = new Foo 创建的动态对象通过 delete p 。如果你忘记了 delete p ,你有一个资源泄露。您不应尝试执行以下操作之一,因为它们都会导致未定义的行为:




  • 通过<$ c $销毁动态对象c> delete [] (请注意方括号), free 或任何其他方法

  • 动态对象多次

  • 在动态对象被销毁后访问动态对象



在动态对象的构造期间抛出,底层内存在异常传播之前被释放。
(析构函数不会在内存释放之前执行,因为对象从未完全构造。)



动态数组



通过 p = new Foo [n] 创建的动态数组通过 delete [ ] p (请注意方括号)。如果你忘记了 delete [] p ,你有一个资源泄露。您不应尝试执行以下操作之一,因为它们都会导致未定义的行为:




  • 通过<$ c $销毁动态数组c> delete , free 或任何其他方法


  • 在销毁后访问动态数组



如果在构建过程中抛出异常第n个元素,元素n-1到0按降序被破坏,底层内存被释放,异常被传播。



(对于动态数组,通常应该优先使用 std :: vector< Foo> over Foo *



一个动态对象由几个<$ p>管理的动态对象在销毁最后的 std :: shared_ptr< Foo> 对象期间销毁对象时会销毁c $ c> std :: shared_ptr< Foo>



(您通常应优先于 std :: shared_ptr< Foo> c> Foo * 。它使得编写正确和健壮的代码更容易。)


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?

(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 this would 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.)

解决方案

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 not destroy 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.

This leads to one of the most important guidelines in 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:

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.

local static objects

Static objects defined inside functions are constructed when (and if) control flow passes through their definition for the first time.1 They are destructed in reverse order after the execution of 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.

1: This is an extremely simplified model. The initialization details of static objects are actually much more complicated.

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:

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 construction of 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 not be executed, since the Foo object was never fully constructed.

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 construction of the n-th element, the elements n-1 to 0 are destructed before the exception is propagated.

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:

__________________________ 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.

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 will be constructed if the prvalue is used to initialize a reference:

const int& r = i + j;

Dynamic objects and arrays

In the following section, destroy X means "first destruct X and then release the underlying memory". Similarly, create X means "first allocate enough memory and then construct X there".

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:

  • 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

If an exception is thrown during the construction of a dynamic object, the underlying memory is released before the exception is propagated. (The destructor will not be 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:

  • 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

If an exception is thrown during the construction 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.

(You should generally prefer std::vector<Foo> over Foo* for dynamic arrays. It makes writing correct and robust code much easier.)

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.

(You should generally prefer std::shared_ptr<Foo> over Foo* for shared objects. It makes writing correct and robust code much easier.)

这篇关于C ++中的对象销毁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆