С++: "MyClass c" плохо, "MyClass c = MyClass()" медленный, мне нужен "MyClass c()",

Вот код:

class MyClass
{
public:
    int y;
};

int main()
{
    MyClass item1;
    MyClass item2 = MyClass();
}

Когда я запустил это, я получаю следующие значения:

item1.y == [garbage]
item2.y == 0

Который, ну, меня удивляет.

Я ожидал, что item1 будет сконфигурирован по умолчанию, а item2 будет скопирован с анонимного экземпляра MyClass, созданного по умолчанию, в результате чего оба равны 0 (поскольку конструкторы-по умолчанию инициализируют члены по умолчанию). Изучение сборки:

//MyClass item1;
//MyClass item2 = MyClass();
xor         eax,eax  
mov         dword ptr [ebp-128h],eax  
mov         ecx,dword ptr [ebp-128h]  
mov         dword ptr [item2],ecx  

Показывает item2, создавая, записывая значение "0" где-то временное, а затем копируя его в item2, как и ожидалось. Однако там нет сборки для item1.

Итак, у программы есть память для item1 в стеке, но она никогда не конструирует item1.

Я могу оценить желание поведения для скорости, но я хочу лучшего из обоих миров! Я хочу знать item1.y == 0 (он создается), но я не хочу тратить время на конструкцию default-construct-anonymous-instance-then-copy-like, как item2.

Разочаровывающе, я не могу заставить конструкцию по умолчанию сказать MyClass item1();, поскольку это интерпретируется как прототип функции.

Итак... если я хочу использовать конструктор по умолчанию для item1 без создания копии, как я это делаю?

Боковое примечание: похоже, если я объявляю конструктор для MyClass, item1 создается как обычно. Поэтому это поведение применяется только к конструкторам, сгенерированным компилятором.

Ответ 1

Сделайте свой класс следующим:

class MyClass 
{
public:
    MyClass() : y(0) 
    {
    }

public:
    int y;
};

и оба примера будут работать нормально. Ваша проблема в том, что если конструктор не поставляется, базовые члены типа будут не инициализироваться. Таким образом, y представляет любые случайные данные, находящиеся в стеке в месте, где находится item1.

Явная реализация конструктора адресует это.

Поведение существует из-за того, что С++ "вы платите только за то, что используете". Базовые типы не имеют "значения по умолчанию", так как это делает ненужным (немного) более дорогостоящим, чтобы выделить что-то, а затем заполнить его позже из-за того, что значения эффективно устанавливаются дважды. Один раз для значения "по умолчанию", один раз для значения, которое вы действительно хотели.

Ответ 2

В С++ вы платите только за то, что вы просите сделать. Возможность иметь типы, которые не инициализируют свои данные, может быть важным повышением производительности: досадно, это также поведение по умолчанию, потому что оно было унаследовано от C.

Вы можете исправить это, создав конструктор, который нуль инициализирует int, используя синтаксис синтаксиса {} или используя новый синтаксис по умолчанию int y = 0; в объявлении типа (для последнего требуется С++ 11, второй требует С++ 11, если тип не является POD).

Относительно производительности, связанной с Foo f = Foo();, вводятся в заблуждение, исследуя сборку отладки. Копирование elision в таком тривиальном случае поддерживается каждым компилятором, не являющимся мозгом, на рынке, и является законным, даже если ctor имеет побочные эффекты.

Ответ 3

Проблема заключается в неправильном понимании двух вещей.

  • Что создает конструктор по умолчанию, созданный компилятором.
  • Что такое объявление (и, следовательно, при назначении).

Что создает компилятор, созданный конструктором по умолчанию.

Если вы не определяете конструктор, компилятор будет генерировать для вас конструктор по умолчанию. Но есть две разные версии. Конструктор по умолчанию "Значение-инициализация" (который для встроенных типов POD ничего не делает и оставляет их неинициализированными). Конструктор по умолчанию "нулевой инициализации" (который для встроенных типов POD устанавливает их в ноль).

Что такое объявление (и, следовательно, при назначении используется).

Оператор присваивания применяется только тогда, когда объект в левой части = был полностью сконструирован. Поскольку объект в объявлении не полностью построен до ';' это не задание.

Bar x = Bar(); // There is no assignment here (this is a declaration using the default constructor
Bar y = Bar(2);// There is no assignment here (this is a declaration using a constructor).

Это построение объекта из временного с использованием конструктора копирования. Но, что не имеет значения, потому что компилятор просто ускоряет фактическую копию и строит на месте, поэтому я был бы полностью удивлен, если бы произошла какая-либо копия.

Что происходит в вашем коде

int  x;          // default-Inititalized.      [ Value = Garbage]
int  z = int();  // Zero-Inititalized.         [ Value = 0]

Те же правила применяются к классам с создаваемыми компилятором конструкторами по умолчанию:

LokiClass  xL;               // Value-Initialized -> Default Initialized
                             //                     This is an explicit call to 
                             //                     the default constructor but  
                             //                     will only Value-Initialize
                             //                     class types and not initialize
                             //                     built-in POD types. 
LokiClass  yL = LokiClass(); // Zero-Initialized    This is an explicit call to 
                             //                     the default constructor but  
                             //                     makes sure to use the 
                             //                     Zero-Initialization version if
                             //                     it is the compiler generated 
                             //                     version.

LokiClass  y1L {};           // C++11 version of Zero-Initialization constructor used.

LokiClass  zL((LokiClass()));// This is copy construction
                             // Which will probably lead to copy elision by the compiler.

Итак, в чем разница между инициализацией Value/Zero?
Инициализация значения не будет выполнять инициализацию для встроенных типов POD. Он будет вызывать созданный конструктор по умолчанию для инициализации значения для любых базовых классов и членов (обратите внимание, что если вы определяете конструктор по умолчанию, то он будет использовать это, потому что нет компилятора, сгенерированного).

Инициализация нуля инициализирует нулевые инициализации типов POD. Он будет вызывать сгенерированный конструктор по умолчанию для нулевого инициализатора по любому базовому классу и членам (обратите внимание, что если вы определяете конструктор по умолчанию, то он будет использовать это, потому что нет компилятора, сгенерированного).

Ответ 4

Вы можете просто сделать:

MyClass item1;

Он может или не может нуль инициализировать тип POD в зависимости от переменной.

Или, если вы используете С++ 11, вы можете сделать:

MyClass item1 {};

чтобы явно вызвать конструктор по умолчанию.

Причина, почему нет кода сборки для элемента 1, заключается в том, что компилятор считает, что нет необходимости генерировать код для класса, подобного показанному, поскольку он не имеет явного конструктора по умолчанию, написанного вами.