在[except.ctor]中,标准(N4140)保证:
...自从输入try块以来构造的所有自动对象都会调用析构函数...
但是在下面的例子中,空输出证明函数的返回值foo
没有被破坏,尽管它已被构造.使用g ++(5.2.1)和clang ++(3.6.2-1)以及选项编译-O0 -fno-elide-constructors -std=c++14
.
struct A { ~A() { cout << "~A\n"; } }; struct B { ~B() noexcept(false) { throw 0; } }; A foo() { B b; return {}; } int main() { try { foo(); } catch (...) { } }
这是g ++和clang ++中的错误,还是函数返回值不被视为自动对象,还是C++语言中的循环漏洞?
在[stmt.return],[expr.call]或[dcl.fct]中都没有找到一个明确的语句,函数返回值是否被视为自动对象.我找到的最接近的提示是6.3.3 p2:
......退货声明可能涉及临时物体的构造和复制或移动......
和5.2.2 p10:
如果结果类型是左值引用类型或对函数类型的右值引用,则函数调用是左值;如果结果类型是对象类型的右值引用,则为xvalue,否则为prvalue.
TartanLlama.. 45
函数返回值被认为是临时值,并且在销毁本地之前对返回值的构造进行排序.
不幸的是,标准中没有详细说明.有一个开放的缺陷描述了这一点并提供了一些解决问题的措辞
[...]具有void类型的操作数的return语句只能在返回类型为cv void的函数中使用.带有任何其他操作数的return语句只能在返回类型不是cv void的函数中使用; return语句初始化要从操作数复制初始化(8.5 [dcl.init])返回的对象或引用.[...]
返回实体的复制初始化在由return语句的操作数建立的全表达式结束时临时销毁之前进行排序,然后在本地变量的销毁之前对其进行排序(6.6 [stmt.包含return语句的块的跳转).
由于函数返回值是临时值,因此destructors are invoked for all automatic objects
在帖子开头的引用中不包括它们.但是,[class.temporary]/3
说:
[...]临时对象被破坏,作为评估(词法)包含创建它们的点的完整表达式的最后一步.即使该评估以抛出异常结束,也是如此.[...]
所以我认为你可以认为这是GCC和Clang中的一个错误.
不要从析构函数中抛出;)
函数返回值被认为是临时值,并且在销毁本地之前对返回值的构造进行排序.
不幸的是,标准中没有详细说明.有一个开放的缺陷描述了这一点并提供了一些解决问题的措辞
[...]具有void类型的操作数的return语句只能在返回类型为cv void的函数中使用.带有任何其他操作数的return语句只能在返回类型不是cv void的函数中使用; return语句初始化要从操作数复制初始化(8.5 [dcl.init])返回的对象或引用.[...]
返回实体的复制初始化在由return语句的操作数建立的全表达式结束时临时销毁之前进行排序,然后在本地变量的销毁之前对其进行排序(6.6 [stmt.包含return语句的块的跳转).
由于函数返回值是临时值,因此destructors are invoked for all automatic objects
在帖子开头的引用中不包括它们.但是,[class.temporary]/3
说:
[...]临时对象被破坏,作为评估(词法)包含创建它们的点的完整表达式的最后一步.即使该评估以抛出异常结束,也是如此.[...]
所以我认为你可以认为这是GCC和Clang中的一个错误.
不要从析构函数中抛出;)
这是一个错误,一次,MSVC实际上是正确的:它打印"~A".
我修改了你的代码,我认为现在从输出我们可以看到A没有被破坏.
#includeusing namespace std; struct A { ~A() { cout << "~A\n"; } A() { cout << "A()"; } }; struct B { ~B() noexcept( false ) { cout << "~B\n"; throw(0); } B() { cout << "B()"; } }; A foo() { B b; return; } int main() { try { foo(); } catch (...) {} }
输出是:
B()A()〜乙
所以是的,它可能是一个错误.