根据C ++ 14 [expr.call] / 4:
参数的生命周期在定义它的函数返回时结束。
这似乎意味着参数的析构函数必须在调用函数的代码继续使用函数的返回值之前运行。
但是,此代码显示不同:
#include <iostream>
struct G
{
G(int): moved(0) { std::cout << "G(int)\n"; }
G(G&&): moved(1) { std::cout << "G(G&&)\n"; }
~G() { std::cout << (moved ? "~G(G&&)\n" : "~G()\n"); }
int moved;
};
struct F
{
F(int) { std::cout << "F(int)\n"; }
~F() { std::cout << "~F()\n"; }
};
int func(G gparm)
{
std::cout << "---- In func.\n";
return 0;
}
int main()
{
F v { func(0) };
std::cout << "---- End of main.\n";
return 0;
}
带有-fno-elide-constructors
的gcc和clang的输出是(带有我的注释):
G(int) // Temporary used to copy-initialize gparm
G(G&&) // gparm
---- In func.
F(int) // v
~G(G&&) // gparm
~G() // Temporary used to copy-initialize gparm
---- End of main.
~F() // v
所以,显然v
的构造函数在gparm
的析构函数之前运行。但是在MSVC中,gparm
在v
的构造函数运行之前被销毁。
启用copy-elision和/或使用func({0})
可以看到同样的问题,因此参数是直接初始化的。 v
总是在gparm
被破坏之前构建。我还在更长的链条中观察到了这个问题,例如在F v = f(g(h(i(j())));
初始化之前,f,g,h,i
没有销毁v
的任何参数。
这在实践中可能是一个问题,例如,如果~G
解锁资源并且F()
获取资源,那将是一个死锁。或者,如果~G
抛出,则执行应该跳转到没有v
初始化的catch处理程序。
我的问题是:该标准是否允许这两种排序? 。是否有更多关于参数破坏的排序关系的具体定义,而不仅仅是expr.call/4中不使用标准排序术语的引用?
答案 0 :(得分:12)
实际上我可以回答我自己的问题...在写作之前没有找到答案,但之后再搜索确实找到了答案(典型的呵呵)。
无论如何:此问题为CWG #1880,其解决方案为:
2014年6月会议的说明:
WG决定不指定参数对象是在调用之后立即销毁还是在调用所属的完整表达式结束时销毁。
我拥有的最新C ++ 17草案(N4606)改变了[expr.call] / 4中的文字:
实现定义参数的生命周期是在定义它的函数返回时还是在封闭的完整表达式的末尾结束。
我想我们应该将此决议(即“实施定义”)视为追溯适用,因为公布的标准没有明确规定。
注意: full-expression 的定义可以在C ++ 14 [intro.execution] / 10中找到:
full-expression是一个表达式,它不是另一个表达式的子表达式。 [...]如果定义语言构造以产生函数的隐式调用,则语言构造的使用被认为是用于此定义目的的表达式。
所以F v { func(0) };
是gparm
的封闭完整表达式(即使它是声明而不是表达式!)。