C++中临时对象析构函数的调用顺序是什么?

5
考虑下面的代码:

考虑下面的代码:

#include <iostream>

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

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

struct C {
  ~C() { std::cout << "~C" << std::endl; }

  void operator<<(const B &) {}
};

C f(const A &a = A()) {
  return C();
}

int main() {
  f(A()) << B();
}

使用GCC编译并运行的结果如下:

~C
~A
~B

如果使用其他编译器编译,是否保证临时对象类型 A、B 和 C 的析构函数将按此顺序调用?总的来说,如果存在临时对象,析构函数的调用顺序是什么?


你使用了哪些优化设置?除非RVO启动,否则这里应该调用~C()两次,对吧? - user1773602
@aleguna: 我正在使用默认设置。我想是 -O0。 - vitaut
1
@aleguna:即使没有启用任何优化,gcc也会执行复制省略。您可以使用“-fno-elide-constructors”禁用它。 - Steve Jessop
3个回答

10
让我们来谈一下子表达式及其排序。如果 E1E2 之前排序,那意味着在 E2 之前必须完全计算 E1。如果 E1E2 不排序,则意味着可以以任意顺序计算 E1E2
对于 f(A()) << B(),在您的情况下与 f(A()).operator<<(B()) 相同,我们知道:
- A()f(...) 之前排序, - f(...)operator<< 之前排序, - B()operator<< 之前排序。
这还告诉我们以下内容:
- A()operator<< 之前排序, - A()B() 不排序, - f(...)B() 不排序。
如果我们假设 RVO,为了不使事情变得复杂,编译器可能计算子表达式的顺序是:
- A() -> f(...) -> B(),产生 ~B() -> ~C() -> ~A(), - A() -> B() -> f(...),产生 ~C() -> ~B() -> ~A(), - B() -> A() -> f(...),产生 ~C() -> ~A() -> ~B()

注意,这里所观察到的顺序是OP中遵循的。需要注意的是,销毁的顺序总是与构造的顺序相反。


A()->B()->f(...) 也是可能的吗? - Grizzly
感谢您提供清晰的答案。标准是否保证析构函数的调用顺序与评估顺序相反? - vitaut
1
@vitaut:析构函数的调用顺序始终与构造顺序相反。我要编辑那部分内容。 - Xeo

3
表达式 f(A()) << B(); 的计算顺序没有规定。因此,构造/销毁的顺序也未指定。

这是我一开始想的,但我不确定是否正确。我的意思是 - 要调用 operator<<,必须已经定义了 C(因为 operator<<C 的成员)。这意味着,在调用 operator<< 之前必须评估 f(A())。这意味着:1-创建 A;2-执行 f(A);3.将 B 作为参数传递。我有什么遗漏吗? - Kiril Kirov
@Kiril C 必须已经在编译时定义,但这只发生在编译时 ;v)。在运行时,无论类型为 C 的对象是在函数的另一个参数之前还是之后创建,都没有关系。 - Potatoswatter
据我所知,A()和B()的评估顺序是未定义的,但C对象呢?它是从函数返回的,所以它不应该在参数之后构造吗?此外,临时对象的销毁顺序是否与构造顺序相反? - vitaut
4
注意,顺序不是未定义的,只是在大多数情况下未指定的 - 例如,必须在评估f(...)之前评估A()。请参见我的答案。 - Xeo

2
<<运算符的操作数的求值顺序是未指定的,因此顺序不保证。只有短路逻辑运算符&&||、三元运算符?:和逗号,运算符有明确定义的操作数求值顺序。对于其他运算符,左操作数不一定在右操作数之前被求值(或反之亦然)。
此外,请不要将运算符优先级或结合性与操作数求值顺序混淆。对于给定的表达式E1 op E2,只需要在应用运算符op之前,同时对E1E2进行求值,但在它们之间,E1E2可以以任何顺序进行求值。
优先级规则决定当表达式中存在多个运算符时,如E1 op1 E2 op2 E3,运算符的应用顺序。
结合性用于确定当同一运算符使用多次时,即在E1 op E2 op E3中,它是解释为(E1 op E2) op E3还是E1 op (E2 op E3)

它是否也适用于重载运算符?如果我将“<<”更改为“,”,那么它能保证顺序吗? - vitaut
你不能通过重载这样改变操作符。而且,就像我说的一样,在操作符被应用之前,两个操作数都必须被评估。在操作数被评估之前,你的重载操作符代码不会被调用。 - Masked Man

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接