函数参数销毁的顺序

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 中,gparmv 的构造函数 运行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 的封闭 完整表达式 (即使它是声明而不是表达式!)。