С++: копировать по значениям в параметры 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
, пока он делает копию в стеке и с какой целью?
Ответы
Ответ 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
.