С++ копировать-конструировать-конструировать-и-назначать вопрос

Вот выдержка из пункта 56 книги "C++ Gotchas":

Нередко можно увидеть простую инициализацию объекта Y, написанную любым из трех разных способов, как если бы они были эквивалентны.

Y a( 1066 ); 
Y b = Y(1066);
Y c = 1066;

На самом деле, все три эти инициализации, вероятно, приведут к созданию одного и того же объектного кода, но они не эквивалентны. Инициализация a известна как прямая инициализация, и она делает именно то, что можно было бы ожидать. Инициализация выполняется прямым вызовом Y::Y(int).

Инициализация b и c более сложна. На самом деле они слишком сложны. Это обе инициализации копирования. В случае инициализации b мы запрашиваем создание анонимного временного объекта типа Y, инициализированного значением 1066. Затем мы используем этот анонимный временный объект в качестве параметра конструктора копирования для класса Y для инициализации b. Наконец, мы вызываем деструктор для анонимного временного объекта.

Чтобы проверить это, я создал простой класс с элементом данных (программа, прикрепленная в конце), и результаты меня удивили. Кажется, что в случае c объект был создан конструктором копирования, а не так, как это предлагается в книге.

Кто-нибудь знает, изменился ли стандарт языка или это просто функция оптимизации компилятора? Я использовал Visual Studio 2008.

Пример кода:

#include <iostream>

class Widget
{
    std::string name;
public:
    // Constructor
    Widget(std::string n) { name=n; std::cout << "Constructing Widget " << this->name << std::endl; }
    // Copy constructor
    Widget (const Widget& rhs) { std::cout << "Copy constructing Widget from " << rhs.name << std::endl; }
    // Assignment operator
    Widget& operator=(const Widget& rhs) { std::cout << "Assigning Widget from " << rhs.name << " to " << this->name << std::endl; return *this; }
};

int main(void)
{
    // construct
    Widget a("a");
    // copy construct
    Widget b(a);
    // construct and assign
    Widget c("c"); 
    c = a;
    // copy construct!
    Widget d = a;
    // construct!
    Widget e = "e";
    // construct and assign
    Widget f = Widget("f");

    return 0;
}

Выход:

Constructing Widget a

Copy constructing Widget from a

Constructing Widget c
Assigning Widget from a to c

Copy constructing Widget from a

Constructing Widget e

Constructing Widget f
Copy constructing Widget from f

Больше всего меня удивили результаты построения d и e. Чтобы быть точным, я ожидал, что будет создан пустой объект, а затем объект будет создан и назначен пустому объекту. На практике объекты создавались конструктором копирования.


person Andy    schedule 17.03.2010    source источник
comment
В случае инициализации b мы запрашиваем создание анонимного временного объекта типа Y, инициализированного значением 1066... ​​и то же самое при инициализации c, просто временный объект сложнее увидеть.   -  person Steve Jessop    schedule 17.03.2010
comment
Обратите внимание, что Y c = 1006 невозможно, если ваш конструктор объявлен как explicit... как и любой конструктор с одним параметром, большую часть времени.   -  person Matthieu M.    schedule 17.03.2010
comment
Матье, да, я согласен с тем, что вы сказали, и я всегда так делаю при программировании. Ваша точка зрения также освещена в книге Более эффективный С++, если я правильно помню.   -  person Andy    schedule 17.03.2010


Ответы (3)


Синтаксис

X a = b;

где a и b относятся к типу X, всегда означало копирование конструкции. Любые варианты, такие как:

X a = X();

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

X a;
a = X();
person Community    schedule 17.03.2010
comment
Спасибо за комментарий по поводу построения копии. Я действительно не знал об этом - я всегда думал, что вы должны сделать X=a(b); чтобы быть уверенным в вызове конструктора копирования. - person Andy; 17.03.2010

Компилятору разрешено оптимизировать случаи b и c, чтобы они были такими же, как a. Кроме того, копирование конструкции и вызовы операторов присваивания могут быть полностью устранены компилятором в любом случае, поэтому все, что вы видите, не обязательно будет одинаковым для разных компиляторов или даже настроек компилятора.

person sbi    schedule 17.03.2010
comment
Значит, теперь книга не может быть на 100% верной? Интересно. - person Andy; 17.03.2010
comment
@ Энди, нет, книга на 100% верна. Но он не завершен на 100%. Если вы хотите 100% завершение, вам необходимо ознакомиться со Стандартом. В частности, он (или часть, которую вы цитируете) не объясняет, что компилятору разрешено оптимизировать копии, если копирование выполняется из временного, как указано выше. - person Johannes Schaub - litb; 17.03.2010
comment
Спасибо. Я думаю, вам все равно следует использовать форматы, предложенные в книге, на случай, если используемый вами компилятор не реализует оптимизацию. - person Andy; 17.03.2010
comment
Нет, не следует. Используйте то, что понятнее, и не беспокойтесь об этих незначительных оптимизациях. - person Matthieu M.; 17.03.2010
comment
@Johannes: В книге правда, и ничего кроме правды, но не вся правда. :) - person sbi; 23.03.2010

Начиная с C++17, все три из этих эквивалентны (если только Y::Y(int) не является explicit, что просто запрещает c) из-за того, что часто называют обязательная копия elision.

Даже Y c = 1066; создает только один объект Y, потому что результатом неявного преобразования в Y является значение prvalue, которое используется для инициализации c, а не для создания временного объекта.

person Davis Herring    schedule 15.09.2017