Подтвердить что ты не робот

С++: копировать по значениям в параметры param производят два объекта в vs2012

Здесь код, который производит разные выходные данные в 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, пока он делает копию в стеке и с какой целью?

4b9b3361

Ответ 1

Кажется, это ошибка компилятора.
В стандарте С++ не используется термин Обрезка объектов. Вы передаете объект типа B функции, которая получает параметр типа A. Компилятор применит обычное разрешение перегрузки, чтобы найти соответствующее совпадение. В этом случае:
Базовый класс A имеет предоставленный компилятором конструктор копирования, который будет ссылаться на A, и в отсутствие других функций преобразования это наилучшее совпадение и должно использоваться компилятором.

Обратите внимание, что если будет доступно лучшее преобразование, оно будет использоваться. Например, если A имеет конструктор A::A( B const& ), в дополнение к конструктору копирования, этот конструктор будет использоваться вместо конструктора копирования.

Ответ 2

Компилятор С++ будет синтезировать конструктор копии по умолчанию в следующей ситуации. (Изнутри объектной модели С++)

  • Когда класс содержит объект-член класса, для которого существует конструктор копирования.
  • Когда класс получен из базового класса, для которого существует конструктор копирования.
  • Когда класс объявляет одну или несколько виртуальных функций
  • Когда класс получен из цепочки наследования, в которой один или несколько базовых классов являются виртуальными.

Мы можем видеть, что класс A не входит в 4 ситуации. Таким образом, cl не синтезирует конструктор копии по умолчанию для него. Может быть, почему 2 объекта temp 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) 

Ответ 3

Вы столкнулись с ошибкой компилятора.

Ниже описана правильная функциональность:


Функция 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.