函数参数的破坏顺序是啥?

Posted

技术标签:

【中文标题】函数参数的破坏顺序是啥?【英文标题】:What is the order of destruction of function arguments?函数参数的破坏顺序是什么? 【发布时间】:2016-08-27 18:50:07 【问题描述】:

如果某个函数 f 带有参数 p_1, ..., p_n 类型分别为 T_1, ..., T_n 被调用参数 a_1, ..., @987654329 @ 及其主体抛出异常,完成或返回,参数以什么顺序被销毁,为什么?如果可能,请提供对标准的参考。

编辑:我实际上想问一下函数“参数”,但作为 T.C.哥伦布设法消除了我的困惑,我将把这个问题留在论点上,并问a new separate question about the parameters。请参阅有关此问题的 cmets 以了解区别。

【问题讨论】:

我不知道顺序,但我猜第二个问题的答案是“因为标准是这样说的”...... 我认为没有预定义的顺序(与调用具有多个参数的函数时相同),但我很高兴看到引用标准的答案。好问题,+1。 wg21.link/cwg1880。这似乎未指定。 @Columbo 不是。我看到人们一直在混合它们。而且因为参数的生命周期不一定与函数的退出有关 - 例如void foo(std::string); std::string s; foo(s); @Columbo 我想我从来没有遇到过对这种区别如此迂腐的人。我认识的大多数人都交替使用参数和参数mainargvargc。不是paramvparamc 【参考方案1】:

我没有设法在标准中找到答案,但我能够在 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,我使用以下 sn-p 来演示这种情况:

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.);

【讨论】:

【参考方案2】:

在第 5.2.2[4] 节中,N3337 非常明确地说明了发生的情况 (online draft):

在参数初始化期间,实现可以通过将相关参数的转换和/或临时对象的构造与 参数的初始化(见 12.2)。参数的生命周期在定义它的函数返回时结束。

例如在

f(g(h()));

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

如果我正确理解了标准中的规定,但是不允许从 h() 返回的值保留到完整表达式的末尾,除非制作了一个副本(参数)并且该副本被销毁一次g() 返回。

两种情况是:

    h返回值用于直接初始化g参数。当g 返回并在调用f 之前,该对象被销毁。 h 返回值是临时的。制作一个副本以初始化g 参数,并在g 返回时将其销毁。原来的临时文件在完整表达式的末尾被销毁。

我不知道实现是否遵循这方面的规则。

【讨论】:

参数对象本身不是临时对象。 T.C.它们是调用者上下文中的临时对象,而被调用者上下文中的命名对象 不是允许编译器避免为函数调用构造临时对象吗? "它们是调用者上下文中的临时对象" citation needed @jotik:不幸的是,C++ 关于这个问题的模糊性仍然存在。问题是临时变量应该在完整表达式结束时被销毁,但参数可能在调用结束时被销毁(!)。从理论上讲,这意味着被调用者销毁参数的 C++ 编译器必须始终从临时参数复制到参数,以便被调用者可以销毁副本,但临时等待函数结束。我认为任何编译器编写者都不会为了遵守荒谬的规则而这样做。我认为(希望)。【参考方案3】:

标准未指定函数参数的评估顺序。来自 C++11 标准 (online draft):

5.2.2 函数调用

8 [ 注意: 后缀表达式和参数表达式的求值都是无序的。参数表达式评估的所有副作用都在函数之前排序 输入(见 1.9)。 ——尾注 ]

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

一个合理的实现会以与构造相反的顺序销毁对象。

【讨论】:

“平台相关”是指“实现定义”? @jotik,是的。 “平台相关”是通俗的说法:) @RSahu 但是作为一个例子,GCC,一个在许多平台上运行的编译器,在 Linux 和 Windows 上可能具有相同的 实现定义 行为,同样可以去为铿锵。规范使用术语定义的实现来表示由实现者决定这里发生什么;它与平台本身关系不大。 @cat,我们这里要扯皮了。 我认为正确的术语实际上是未指定。 ISO 确实将实现定义作为一个类别,但这是一个更强的要求:它的字面意思是实现必须公开定义他们做出的选择。未指定意味着选择可能因版本而异,甚至取决于编译器设置。

以上是关于函数参数的破坏顺序是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在“函数参数”中通过const类型在派生类中使用不同函数参数的虚函数会破坏虚函数吗? [重复]

JavaScript中一个对象中的默认破坏函数参数

c++函数重载,具体步骤是啥

AfxBeginThread 破坏了作为参数传递给线程函数的类中的 LPWSTR 值。为啥?

vs函数不接受一个参数是啥问题呢

recv函数的第四个参数是啥意思