在C++中为临时库调用析构函数的顺序是什么

What is the order of calling destructors for temporaries in C++?

本文关键字:析构函数 调用 顺序 是什么 C++      更新时间:2023-10-16

考虑以下代码:

#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的临时对象的析构函数将按此顺序调用?一般来说,如果存在临时性,那么析构函数调用临时性的顺序是什么?

让我们来谈谈子表达式及其排序。如果E1E2之前被排序,则意味着E1必须在E2之前被完全求值。如果E1E2一起被unsequence,则意味著E1E2可以按任何顺序求值。

对于f(A()) << B(),在您的情况下与f(A()).operator<<(B())相同,我们知道:

  • CCD_ 11在CCD_
  • CCD_ 13在CCD_
  • 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中观察到的顺序。请注意,破坏的顺序总是与构造的顺序相反。

未指定表达式f(A()) << B();的求值顺序。因此,建造/销毁的顺序也没有具体说明。

未指定<<操作数的求值顺序。因此,秩序无法得到保证。只有短路运算符&&||、三元运算符?:和逗号,运算符具有明确定义的操作数求值顺序。对于其他操作数,不必先计算左操作数,再计算右操作数(反之亦然)。

此外,不要将运算符优先级或关联性与求值顺序混淆。对于给定的表达式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),关联性用于决定哪些操作数绑定到哪个运算符。