Visual Studio 2008 下的虚假 C++ 析构函数调用(GCC 下不存在)
Spurious C++ destructor call under Visual Studio 2008 (absent under GCC)
采用以下(人为的)class 从构造函数和析构函数打印到控制台的层次结构:
#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;
}
使用 gcc 在 linux 下编译,它按预期打印 AB~AX~B~A
(X
之前打印的 ~A
是按值传递给 func
创建一个副本,当函数 returns) 时被破坏。
但是在 windows 下使用 VS2008 编译它打印 AB~A~AX~B~A
- 额外的 ~A
来自哪里?如果复制 xtor 被显式定义 A(A& that) {};
或者如果析构函数被声明为虚拟的(可以说它应该是虚拟的),它就会消失。
评论表明 MSVC 2008 使用 g++ 不使用的临时变量来传递参数。如果是这样,这是一个错误。从 C++03 [dcl.init]/12:
The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form
T x = a;
现在到了关键点。在 T x = a;
中,如果 a
不是 不是 一个 T
或从 T
派生的,那么它等同于 T x = T(a);
,并且在概念上使用了一个额外的临时文件。 (此临时文件符合复制省略的条件)。
但是,如果 a
是 T
或派生自 T
,则不能有额外的 temporary.It 与 T x(a);
相同.
在这个问题的代码中,由于 B
派生自 A
,所以不能有临时的。
C++03 中的支持文本在 [dcl.init]/14 下(我已突出显示与该问题的代码示例相关的部分):
If the destination type is a (possibly cv-qualified) class type:
- If the class is an aggregate (8.5.1), and the initializer is a brace-enclosed list, see 8.5.1.
- If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression(s) as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
- Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an imple-
mentation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
采用以下(人为的)class 从构造函数和析构函数打印到控制台的层次结构:
#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;
}
使用 gcc 在 linux 下编译,它按预期打印 AB~AX~B~A
(X
之前打印的 ~A
是按值传递给 func
创建一个副本,当函数 returns) 时被破坏。
但是在 windows 下使用 VS2008 编译它打印 AB~A~AX~B~A
- 额外的 ~A
来自哪里?如果复制 xtor 被显式定义 A(A& that) {};
或者如果析构函数被声明为虚拟的(可以说它应该是虚拟的),它就会消失。
评论表明 MSVC 2008 使用 g++ 不使用的临时变量来传递参数。如果是这样,这是一个错误。从 C++03 [dcl.init]/12:
The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form
T x = a;
现在到了关键点。在 T x = a;
中,如果 a
不是 不是 一个 T
或从 T
派生的,那么它等同于 T x = T(a);
,并且在概念上使用了一个额外的临时文件。 (此临时文件符合复制省略的条件)。
但是,如果 a
是 T
或派生自 T
,则不能有额外的 temporary.It 与 T x(a);
相同.
在这个问题的代码中,由于 B
派生自 A
,所以不能有临时的。
C++03 中的支持文本在 [dcl.init]/14 下(我已突出显示与该问题的代码示例相关的部分):
If the destination type is a (possibly cv-qualified) class type:
- If the class is an aggregate (8.5.1), and the initializer is a brace-enclosed list, see 8.5.1.
- If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression(s) as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
- Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an imple- mentation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.