函数参数销毁的顺序
Sequencing of function parameter destruction
根据 C++14 [expr.call]/4:
The lifetime of a parameter ends when the function in which it is defined returns.
这似乎暗示参数的析构函数必须 运行 在调用函数的代码继续使用函数的 return 值之前。
但是,此代码显示不同:
#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
的构造函数 运行s.
之前被销毁
启用复制省略可以看到相同的问题,and/or 和 func({0})
以便直接初始化参数。 v
总是在 gparm
被破坏之前构造。我还在更长的链中观察到这个问题,例如F v = f(g(h(i(j())));
直到 v
初始化后才破坏 f,g,h,i
的任何参数。
这在实践中可能是一个问题,例如,如果 ~G
解锁了一个资源并且 F()
获取了资源,这将是一个死锁。或者,如果 ~G
抛出异常,则执行应跳转到未初始化 v
的捕获处理程序。
我的问题是:标准是否允许这两种排序? .对于涉及参数破坏的排序关系,是否有比仅引用 expr.call/4 不使用标准排序术语的引用更具体的定义?
其实我可以回答我自己的问题...在写之前搜索时没有找到答案,但是后来再次搜索确实找到了答案(典型的呵呵)。
无论如何:这个问题 CWG #1880 有以下注释:
Notes from the June, 2014 meeting:
WG decided to make it unspecified whether parameter objects are destroyed immediately following the call or at the end of the full-expression to which the call belongs.
尽管问题 1880 仍未解决。
P0135 - guaranteed copy-elision 也访问了该主题,这使得它是实现定义的,而不是未指定的。在 C++17 (N4659) 中,文本是:
It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression.
这里有更多的背景信息:
注意:full-expression的定义可以在C++14 [intro.execution]/10:
中找到
A full-expression is an expression that is not a subexpression of another expression. [...] If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
所以 F v { func(0) };
是 gparm
的封闭 完整表达式 (即使它是声明而不是表达式!)。
根据 C++14 [expr.call]/4:
The lifetime of a parameter ends when the function in which it is defined returns.
这似乎暗示参数的析构函数必须 运行 在调用函数的代码继续使用函数的 return 值之前。
但是,此代码显示不同:
#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
的构造函数 运行s.
启用复制省略可以看到相同的问题,and/or 和 func({0})
以便直接初始化参数。 v
总是在 gparm
被破坏之前构造。我还在更长的链中观察到这个问题,例如F v = f(g(h(i(j())));
直到 v
初始化后才破坏 f,g,h,i
的任何参数。
这在实践中可能是一个问题,例如,如果 ~G
解锁了一个资源并且 F()
获取了资源,这将是一个死锁。或者,如果 ~G
抛出异常,则执行应跳转到未初始化 v
的捕获处理程序。
我的问题是:标准是否允许这两种排序? .对于涉及参数破坏的排序关系,是否有比仅引用 expr.call/4 不使用标准排序术语的引用更具体的定义?
其实我可以回答我自己的问题...在写之前搜索时没有找到答案,但是后来再次搜索确实找到了答案(典型的呵呵)。
无论如何:这个问题 CWG #1880 有以下注释:
Notes from the June, 2014 meeting:
WG decided to make it unspecified whether parameter objects are destroyed immediately following the call or at the end of the full-expression to which the call belongs.
尽管问题 1880 仍未解决。
P0135 - guaranteed copy-elision 也访问了该主题,这使得它是实现定义的,而不是未指定的。在 C++17 (N4659) 中,文本是:
It is implementation-defined whether the lifetime of a parameter ends when the function in which it is defined returns or at the end of the enclosing full-expression.
这里有更多的背景信息:
注意:full-expression的定义可以在C++14 [intro.execution]/10:
中找到A full-expression is an expression that is not a subexpression of another expression. [...] If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition.
所以 F v { func(0) };
是 gparm
的封闭 完整表达式 (即使它是声明而不是表达式!)。