16

根据 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;
}

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

所以,显然v' 的构造函数在gparm' 的析构函数之前运行。但在 MSVC 中,在的构造函数运行gparm之前被销毁。v

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

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

我的问题是:标准是否允许这两种排序?. 除了不使用标准排序术语的 expr.call/4 中的引用之外,还有没有更具体的涉及参数破坏的排序关系的定义?

4

1 回答 1

12

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

无论如何:这个问题是CWG #1880,带有以下注释:

2014 年 6 月会议记录:

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

虽然问题 1880 仍然开放。

The subject was also visited by P0135 - guaranteed copy-elision which made it implementation-defined, rather than unspecified. In C++17 (N4659) the text is:

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.

There is more background information here: Late destruction of function parameters


Note: The definition of full-expression can be found in 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.

So F v { func(0) }; is the enclosing full-expression for gparm (even though it's a declaration and not an expression!).

于 2016-10-03T05:06:13.730 回答