38

在下面的 C++ 代码中,我是否保证在执行 // More code之后会调用 ~obj() 析构函数?或者如果编译器检测到它没有被使用,是否允许编译器更早地破坏它?

{
  SomeObject obj;
  ... // More code
}

我想使用这种技术来避免我不得不记住在块的末尾重置一个标志,但我需要这个标志在整个块中保持设置。

4

8 回答 8

47

你可以接受 - 这是 C++ 编程中非常常用的模式。来自 C++ 标准第 12.4/10 节,指的是何时调用析构函数:

对于创建对象的块退出时具有自动存储持续时间的构造对象

于 2010-01-18T16:43:52.280 回答
26

实际上...

C++ 有一种叫做“好像”的原则。所有这些答案中引用的所有保证仅指可观察到的行为。允许编译器逃避、重新排序、添加等任何函数调用,只要可观察到的行为就好像它按照最初编写的那样执行。这也适用于析构函数。

因此,从技术上讲,您的观察是正确的:如果编译器检测到该对象未被使用,则允许编译器更早地破坏该对象,并且析构函数或其调用的任何函数都没有可观察到的副作用。但是,保证您无法判断这是在调试器之外发生的,因为如果您能够判断,编译器将不再能够做到这一点。

然而,编译器更有可能使用这种能力来做一些有用的事情,比如完全避开一个微不足道的析构函数,而不是实际重新排序析构函数调用。

编辑:有人想要参考... 1.9/5,以及 C++0x 草案标准的脚注 4(这不是新规则,我只是手边没有 C++03 标准。它也是存在于 C 标准中,AFAIK)

1.9/5:

执行格式良好的程序的一致实现应产生与具有相同程序和相同输入的抽象机的相应实例的可能执行序列之一相同的可观察行为。但是,如果任何这样的执行序列包含未定义的操作,则本国际标准对使用该输入执行该程序的实现没有要求(甚至不考虑第一个未定义操作之前的操作)。

脚注 4:

这个规定有时被称为“好像”规则,因为只要从可观察的行为中可以确定,只要结果是好像已经遵守了要求,实施就可以自由地忽略本国际标准的任何要求的程序。例如,如果一个实际的实现可以推断出它的值没有被使用并且没有产生影响程序可观察行为的副作用,那么它就不需要评估表达式的一部分。

我的阅读(以及我认为的一般理解)是,只要可观察到的行为是原始书面源的行为 - 包括移动围绕析构函数,根本不破坏对象,发明析构函数等。

于 2010-01-18T18:01:46.037 回答
19

在对象超出范围之前,不会调用析构函数。

C++ faq lite在dtors 上有一个很好的部分

于 2010-01-18T16:42:30.643 回答
9

C++ 中的破坏是确定性的——这意味着编译器不能随意移动该代码。(当然优化可能会内联析构函数,确定析构函数代码不与之交互// More code并进行一些指令重新排序,但这是另一个问题)

如果您不能依赖在应该调用它们时调用的析构函数,则不能使用 RAII 来获取锁(或任何其他 RAII 构造):

{
    LockClass lock(lockData);
    // More code
} // Lock automatically released.

此外,您可以依赖以与对象构造相反的顺序运行的析构函数。

于 2010-01-18T16:48:38.150 回答
5

是的,这是有保证的。

具有自动存储持续时间的对象的生命周期在其潜在范围结束时结束,而不是之前。对于这样的对象,潜在范围从声明点开始,到声明它的块的末尾结束。这是调用析构函数的时刻。

请注意,非常迂腐地说,即使对于自动对象,当它“超出范围”(而不是“超出其潜在范围”)时说它被销毁也是不正确的。对象可以多次超出范围并返回范围(如果在块内声明了更多同名的本地对象),并且以这种方式超出范围不会导致对象的破坏。杀死自动对象的是其范围的“最终结束”,它被定义为如上所述的其潜在范围的结束。

事实上,语言标准甚至不依赖范围的概念来描述自动对象的生命周期(无需处理所有这些复杂的术语)。它只是说对象在定义它的块的出口处被销毁:)

于 2010-01-18T16:51:18.550 回答
4

这里的所有答案都解决了命名对象的情况,但为了完整起见,您可能也应该知道临时/匿名对象的规则。(例如f(SomeObjectConstructor()f(someFunctionThatReturnsAnObject())

临时对象被销毁作为评估完整表达式(1.9)的最后一步,该完整表达式(在词法上)包含它们被创建的点。即使评估以抛出异常结束也是如此。(来自 ISO C++98 标准的 12.2/3)

这基本上意味着临时生成的对象会持续到下一条语句。两个例外是作为对象初始化列表的一部分生成的临时对象(在这种情况下,临时对象仅在对象完全构造后被销毁)以及对临时对象的引用(例如const Foo& ref = someFunctionThatReturnsAnobject())(在这种情况下对象的生命周期是参考的生命周期)。

于 2010-01-18T19:34:34.947 回答
3

是的,C++ 标准对何时销毁对象有非常具体的要求(在 §12.4/10 中),在这种情况下,必须在块中的所有其他代码完成执行之后才能销毁它。

于 2010-01-18T16:48:19.967 回答
1

一个典型的例子,就像你的问题是 boost::scoped_ptr (或类似的 std::auto_ptr) :

{
    boost::scoped_ptr< MyClass > pMyClass( new MyClass );

    // code using pMyClass here

} // destruction of MyClass and memory freed
于 2010-01-18T16:48:54.560 回答