是否保证在块结束之前不会调用C++析构函数?

J F*_*cis 34 c++ raii

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

{
  SomeObject obj;
  ... // More code
}
Run Code Online (Sandbox Code Playgroud)

我想使用这种技术来节省我必须记住在块的末尾重置一个标志,但我需要为整个块保持设置的标志.

小智 46

你没关系 - 这是C++编程中一种非常常用的模式.从C++标准第12.4/10节开始,指的是何时调用析构函数:

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

  • +1此外,`3.7.2/3`强化:"如果命名的自动对象具有初始化或具有副作用的析构函数,则它不应在其块结束之前销毁,也不应作为优化消除,即使它似乎未被使用" (20认同)

Ter*_*fey 24

其实...

C++有一种称为"似乎"的原则.在所有这些答案中引用的所有guarentees仅涉及可观察的行为.允许编译器对任何函数调用进行排除,重新排序,添加等等,只要可观察行为就像它最初编写的那样执行.这也适用于析构函数.

因此,从技术上讲,您的观察是正确的:如果检测到它没有被使用,允许编译器更早地破坏对象,并且析构函数或它调用的任何函数都没有可观察到的副作用.但是,你可以保证在调试器之外无法判断这种情况,因为如果你能够分辨,编译器将无法再这样做.

编译器使用这种功能更有可能做一些有用的事情,比如完全抛弃一个简单的析构函数,而不是实际重新排序析构函数调用.

编辑:有人想要一个引用... 1.9/5,以及C++ 0x草案标准的脚注4(这不是一个新规则,我只是没有C++ 03标准方便.它也是出现在C标准中,AFAIK)

1.9/5:

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

脚注4:

这项规定有时被称为"假设"规则,因为只要结果就像是遵守了要求,只要可以从可观察的行为中确定,实施就可以自由地忽视本国际标准的任何要求.该计划.例如,实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用,并且没有产生影响程序的可观察行为的副作用.

我的阅读(以及我认为的一般理解)是,只要可观察的行为是原始书面来源的行为 - 包括移动,这就是使编译器可以随心所欲地做任何事情(即,启用优化)的原因.在析构函数周围,根本不破坏对象,发明析构函数等.

  • 好吧,他是对的,而且litb的评论支持它.如果析构函数有副作用**,那引用说**.重点是,如果它没有区别,编译器可能会重新排序.但当然这是一个学术观点,因为这可能会在没有任何区别的情况下发生,因此不值得担心. (4认同)
  • @Neil"只要结果好像符合要求,就可以自由地忽视本国际标准的任何要求" (3认同)
  • 副作用与可观察行为不同.例如`i ++;`无论`i`是否易变,都有副作用.但如果我没有动荡,它就没有可观察到的行为.但我认为我同意它并不重要:如果在它的构造函数之外没有观察到副作用,我认为实现可以优化变量.这只是一个措辞问题,但我认为意图很清楚. (2认同)

Joh*_*don 18

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

C++ FAQ精简版有dtors良好的部分


Ecl*_*pse 9

C++中的销毁是确定性的 - 意味着编译器无法自由移动代码.(当然优化可能会内联析构函数,确定析构函数代码不与之交互// More code并执行一些指令重新排序,但这是另一个问题)

如果你不能依赖于被调用时调用的析构函数,你就不能使用RAII来获取锁(或者就任何其他RAII结构而言):

{
    LockClass lock(lockData);
    // More code
} // Lock automatically released.
Run Code Online (Sandbox Code Playgroud)

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

  • C++标准是我和编译器之间的契约:如果我编写标准C++,编译器应该使它以符合标准的方式运行.如果编译器发出在特定处理器上运行不正确的目标代码,则它不符合该处理器.编译器的工作是确保处理器管道正确运行我的代码,而不是我的代码. (2认同)

AnT*_*AnT 5

是的,它是有保证的.

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

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

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

  • 潜在范围与范围之差+1。这种潜在范围和范围的差异最近让我感到害怕。事实上,该标准说“从具有自动存储持续时间的局部变量不在范围内的点跳转到它在范围内的点的程序是格式错误的......”。所以它实际上表明这是格式错误的:`{ string a; { int a; 转到富;} 富:; }`。当然这不是本意:) (2认同)