Visual Studio 2008 下的虚假C++析构函数调用(在 GCC 下不存在)

Spurious C++ destructor call under Visual Studio 2008 (absent under GCC)

本文关键字:函数调用 GCC 不存在 析构 C++ 2008 Studio Visual      更新时间:2023-10-16

采用以下(人为的(类层次结构,该层次结构从构造函数和析构函数打印到控制台:

#include <iostream>
class A { 
public:
  A() { std::cout << "A"; }
  ~A() { std::cout << "~A"; } 
};
class B : public A { 
public:   
  B() { std::cout << "B"; }
  ~B() { std::cout << "~B"; } 
};
void func(A a) { }
int main() {   
  B b;
  func(b);
  std::cout << "X";
  return 0; 
}

在 linux 下使用 gcc 编译,它按预期打印AB~AX~B~A(X之前打印的~A是按值传递到 func 的结果,这会创建一个副本,该副本在函数返回时被销毁(。

但是在带有VS2008的Windows下编译它打印AB~A~AX~B~A - 额外的~A从何而来?如果A(A& that) {};显式定义了复制 xtor,或者析构函数被声明为虚拟(可以说应该是这样(,它就会消失。

注释表明 MSVC 2008 使用 g++ 不使用的临时来传递参数。如果是这样,这是一个错误。从 C++03 [dcl.init]/12

在参数传递、函数返回、引发异常 (15.1(、处理异常 (15.3( 和大括号括起来的初始值设定项列表 (8.5.1( 中发生的初始化称为复制初始化,等效于 T x = a;

现在这是关键的一点。 在T x = a;中,如果a不是T或派生自T,则等价于T x = T(a);,并且在概念上使用了额外的临时。(此临时人员有资格复制省略(。

但是,如果aT或派生自T,则不得有额外的临时。它与T x(a);相同。

在这个问题的代码中,既然B派生自A,就不能有临时的。

C++03 中的支持文本位于 [dcl.init]/14 下(我突出显示了与此问题的代码示例相关的部分(:

如果目标类型是(可能符合 cv 条件的(类类型:

  • 如果类是聚合 (8.5.1(,并且初始值设定项是大括号括起来的列表,请参阅 8.5.1。
  • 如果初始化是直接初始化,或者如果是复制初始化,其中源类型的 cv 非限定版本与目标类的类相同,或者是目标类的派生类,则考虑构造函数。枚举适用的构造函数 (13.3.1.3(,并通过重载解析 (13.3( 选择最佳构造函数。调用这样选择的构造函数来初始化对象,并将初始值设定项表达式作为其参数。如果未应用构造函数,或者重载解析不明确,则初始化格式不正确。
  • 否则(即,对于剩余的复制初始化情况(,如 13.3.1.4 中所述枚举可以从源类型转换为目标类型或(使用转换函数时(转换为其派生类的用户定义的转换序列,并通过重载分辨率 (13.3( 选择最佳转换序列。如果转换无法完成或不明确,则初始化格式不正确。使用初始值设定项表达式作为其参数调用所选函数;如果函数是构造函数,则调用将初始化目标类型的临时函数。然后,调用的结果(构造函数情况的临时结果(用于根据上述规则直接初始化作为复制初始化目标的对象。在某些情况下,一个 允许通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;参见 12.2、12.8。