破坏函数参数的顺序是什么?

jot*_*tik 35 c++ arguments function object-lifetime language-lawyer

如果某些函数f带有参数p_1,...,p_n类型T_1,...,T_n分别用参数调用a_1,......,a_n并且它的正文抛出异常,则完成或返回,参数被破坏的顺序是什么?为什么?如果可能,请提供标准参考.

编辑:我实际上想询问函数"参数",但是由于TC和Columbo设法清除了我的困惑,我将这个问题留下来讨论参数并询问一个关于参数的新单独问题.有关区别,请参阅有关此问题的评论.

Zer*_*ges 21

我无法在标准中找到答案,但我能够在3个最受欢迎的C++兼容编译器上进行测试.R Sahu的答案几乎解释了它是实现定义的.

§5.2.2/ 8:对后缀表达式和参数的评估都是相对于彼此的无法排序的.在输入函数之前,对参数评估的所有副作用进行排序.

Visual Studio C++编译器(Windows)和gcc(Debian)
参数的构造顺序与它们的声明相反,并以相反的顺序销毁(因此按照退出的顺序销毁):

2
1
-1
-2

Clang(FreeBSD)
参数按其声明的顺序构建,并按相反的顺序销毁:

1
2
-2
-1

所有编译器都被指示将源代码视为C++ 11,我使用以下代码片段来演示这种情况:

struct A
{
    A(int) { std::cout << "1" << std::endl; }
    ~A() { std::cout << "-1" << std::endl; }
};

struct B
{
    B(double) { std::cout << "2" << std::endl; }
    ~B() { std::cout << "-2" << std::endl; }
};

void f(A, B) { }

int main()
{
    f(4, 5.);
}
Run Code Online (Sandbox Code Playgroud)


650*_*502 14

在§5.2.2[4]中,N3337对发生的事情非常明确(在线草案):

在参数的初始化期间,实现可以通过将关联参数上的转换和/或临时构造与参数的初始化相结合来避免构建额外的临时值(参见12.2).参数的生命周期在定义它的函数返回时结束.

所以例如在

f(g(h()));
Run Code Online (Sandbox Code Playgroud)

调用的返回值h()是一个临时值,将在完整表达式结束时销毁.但是,允许编译器避免这种临时性,并使用其值参数直接初始化g().在这种情况下,返回值将在返回时被销毁g()(即在调用之前f()).

如果我正确理解标准中所述的内容,则不允许将返回的值保存h()到完整表达式的末尾,除非复制(参数)并且一旦g()返回就销毁该副本.

这两种情况是:

  1. h返回值用于直接初始化g参数.g返回时和调用之前,此对象将被销毁f.
  2. h返回值是暂时的.复制是为了初始化g参数,它在g返回时被销毁.相反,原始临时表在完整表达式的末尾被销毁.

我不知道实现是否遵循相关规则.

  • "他们是来电者背景下的临时工"{{citation needed}} (3认同)
  • @jotik:遗憾的是,无论如何都存在关于这个问题的C++模糊性.问题是临时表应该在完整表达式的末尾被销毁,但参数**可能会在调用结束时被销毁(!).这理论上意味着被调用者销毁参数的C++编译器必须始终从临时参数复制到参数,以便被调用者可以销毁副本,但临时等待函数结束.我认为任何编译器编写者都不会这样做,以遵守荒谬的规则.我想(希望). (2认同)

R S*_*ahu 11

标准未指定评估函数的参数的顺序.从C++ 11标准(在线草案):

5.2.2函数调用

8 [ 注意:后缀表达式和参数表达式的评估都是相对于彼此的未经测序的.在输入函数之前,对参数表达式评估的所有副作用进行排序(参见1.9).- 尾注 ]

因此,完全取决于实现来决定以什么顺序来评估函数的参数.反过来,这意味着参数的构造顺序也依赖于实现.

合理的实施将以与其构造相反的顺序破坏对象.

  • "平台依赖"是指"实现定义"? (3认同)
  • 我认为正确的术语实际上是_unspecified_.ISO确实将_implementation定义为一个类别,但这是一个更强烈的要求:它实际上意味着实现必须公开定义他们做出的选择.未指定意味着选择可能因发行版而异,甚至取决于编译器设置. (3认同)