T_1
, ..., T_n
的参数p_1
, ..., p_n
的函数f
被调用时,使用参数a_1
, ..., a_n
,并且当其主体抛出异常、结束或返回时,参数的销毁顺序是什么,为什么?如果可能,请提供对标准的引用。
编辑: 实际上我想问的是关于函数“参数”的问题,但是由于T.C.和Columbo已经澄清了我的困惑,所以我保留这个问题关于参数,同时提出一个新的单独问题来区分两者。请参见此问题的评论。
T_1
, ..., T_n
的参数p_1
, ..., p_n
的函数f
被调用时,使用参数a_1
, ..., a_n
,并且当其主体抛出异常、结束或返回时,参数的销毁顺序是什么,为什么?如果可能,请提供对标准的引用。
编辑: 实际上我想问的是关于函数“参数”的问题,但是由于T.C.和Columbo已经澄清了我的困惑,所以我保留这个问题关于参数,同时提出一个新的单独问题来区分两者。请参见此问题的评论。
我没有在标准中找到答案,但我能够在三个最流行的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.);
}
在§5.2.2[4]中,N3337非常明确地阐述了发生的情况(在线草案):
在参数初始化期间,实现可以通过将相关实参的转换和/或临时对象的构造与参数的初始化组合起来,以避免构造额外的临时对象 (见12.2)。参数的生命周期在定义该参数的函数返回时结束。
例如,在以下情况下:
f(g(h()));
调用h()
的返回值是一个临时对象,将在完整表达式结束时被销毁。然而编译器允许避免使用这个临时对象,并直接使用它的值初始化g()
的参数。在这种情况下,返回值将在g()
返回之前被销毁(即在调用f()
之前)。
如果我正确理解标准中所述的内容,那么除非进行复制(参数),否则不允许使从h()
返回的值在完整表达式结束之前存活。
两种情况如下:
h
的返回值用于直接初始化g
的参数。该对象在g
返回之前被销毁,并在调用f
之前。h
的返回值是一个临时对象。复制一份以初始化g
参数,并在g
返回时销毁该副本。原始临时对象在完整表达式结束时被销毁。我不知道实现是否遵循这些规则。
因此,完全由实现来决定评估函数参数的顺序。这又意味着参数的构造顺序也取决于实现。5.2.2 函数调用
8 [ 注: 后缀表达式和实参表达式的求值之间是未排序的。所有实参表达式的副作用都在进入函数之前被排序 (见1.9). —end note ]
void foo(std::string); std::string s; foo(s);
- T.C.main
函数中,使用的是argv
和argc
,而不是paramv
和paramc
。 - Rob K