功能参数破坏的排序

M.M*_*M.M 16 c++ function-calls language-lawyer sequencing c++14

根据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;
}
Run Code Online (Sandbox Code Playgroud)

gcc和clang的输出-fno-elide-constructors是(带有我的注释):

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
Run Code Online (Sandbox Code Playgroud)

所以,显然v构造函数在gparm析构函数之前运行.但是在MSVC中,gparm在v构造函数运行之前就被销毁了.

启用copy-elision时可以看到相同的问题,和/或func({0})使参数直接初始化.v在gparm被破坏之前总是被构造出来.我还在更长的链中观察到这个问题,例如在初始化之前F v = f(g(h(i(j())));没有销毁任何参数.f,g,h,iv

这在实践中可能是一个问题,例如,如果~G解锁资源并F()获取资源,那将是一个死锁.或者,如果~G抛出,则执行应跳转到catch处理程序而不v进行初始化.

我的问题是:该标准是否允许这两种排序?.是否有更多关于参数破坏的排序关系的具体定义,而不仅仅是expr.call/4中不使用标准排序术语的引用?

M.M*_*M.M 12

实际上我可以回答我自己的问题...在写作之前没有找到答案,但之后再次搜索确实找到了答案(典型的呵呵).

无论如何:这个问题是CWG#1880的决议:

2014年6月会议记录:

WG决定不指定参数对象是在调用之后立即销毁还是在调用所属的完整表达式结束时销毁.

我拥有的最新C++ 17草案(N4606)改变了[expr.call]/4中的文本:

实现定义参数的生命周期是在定义它的函数返回时还是在封闭的完整表达式的末尾结束.

我想我们应该将此决议(即"实施定义")视为追溯适用,因为已公布的标准没有明确规定.

注意:完整表达式的定义可以在C++ 14 [intro.execution]/10中找到:

完整表达式是不是另一个表达式的子表达式的表达式.[...]如果定义语言构造以产生函数的隐式调用,则语言构造的使用被认为是用于该定义目的的表达式.

所以F v { func(0) };是封闭充分表达了gparm(尽管这是一个声明,而不是表达!).