Visual C++:按值复制到函数参数在 VS2012 中生成两个对象

visual c++: copy by value to function params produce two objects in vs2012

本文关键字:对象 两个 VS2012 C++ 复制 参数 函数 Visual      更新时间:2023-10-16

这是在 g++ 4.7 和 vs2012 (cl17) 中生成不同输出的代码。

#include <iostream>
using namespace std;
class A
{
public:
    A() { cout << "1" << endl; }
    ~A() { cout << "2" << endl; }
};
class B : public A
{
public:
    B() { cout << "3" << endl; }
    ~B() { cout << "4" << endl; }
};
void func(A a) {}
int main()
{
    B b;
    func(b);
    return 0;
}

GCC 输出13242,而 cl 输出132242

为什么 cl 编译器在堆栈上创建副本时会生成第二个 A 对象,目的是什么?

这似乎是一个编译器错误。
C++标准不使用术语对象切片,您正在将类型B的对象传递给接收类型为A的参数的函数。编译器将应用通常的重载分辨率来查找适当的匹配项。在这种情况下:
基类A具有编译器提供的复制构造函数,它将引用A,在没有其他转换函数的情况下,这是最佳匹配,应由编译器使用。

请注意,如果有更好的转换可用,则会使用它。例如:如果A有一个构造函数A::A( B const& ),除了复制构造函数之外,那么将使用此构造函数,而不是复制构造函数。

C++编译器将在以下情况下合成默认的复制构造函数。(从对象模型内部C++)

  1. 当类包含存在复制构造函数的类的成员对象时。
  2. 当类派生自存在复制构造函数的基类时。
  3. 当类声明一个或多个虚函数时
  4. 当类派生自一个或多个基类是虚拟的继承链时。

我们可以看到 A 类不在 4 种情况下。所以 cl 不要为它合成默认的复制构造函数。也许这就是为什么 2 个临时 A 对象构建和销毁的原因。

从 disassemly 窗口中,我们可以看到以下代码,没有调用 A::A。

B b;
00B317F8  lea         ecx,[b]  
00B317FB  call        B::B (0B31650h)  
00B31800  mov         dword ptr [ebp-4],0  
func(b);
00B31807  mov         al,byte ptr [ebp-12h]  
00B3180A  mov         byte ptr [ebp-13h],al  
00B3180D  mov         byte ptr [ebp-4],1  
00B31811  movzx       ecx,byte ptr [ebp-13h]  
00B31815  push        ecx  
00B31816  call        func (0B31730h)  

但是,如果我们使析构函数成为虚拟的。我们将得到以下反汇编代码,我们可以看到调用了 A::A。然后结果是预期的,只创建了 1 个对象。

B b;
00331898  lea         ecx,[b]  
0033189B  call        B::B (03316A0h)  
003318A0  mov         dword ptr [ebp-4],0  
func(b);
003318A7  push        ecx  
003318A8  mov         ecx,esp  
003318AA  mov         dword ptr [ebp-1Ch],esp  
003318AD  lea         eax,[b]  
003318B0  push        eax  
003318B1  call        A::A (0331900h)  
003318B6  mov         dword ptr [ebp-20h],eax  
003318B9  call        func (03317D0h) 

你遇到了编译器的错误。

正确的功能解释如下:


该函数func需要创建对象的副本(但要注意切片)。

所以,发生的事情是这样的:

int main()
{
    // create object B, which first creates the base object A
    B b;
    // create object A, using this copy constructor : A( const B& )
    func(b);
}

额外的 ~A() 调用是在复制构造的对象 A 在func调用结束时被销毁时进行的。

相关文章: