Почему не вызывается конструктор копии?

180
02 февраля 2018, 22:15

Имею такой код

 #include <iostream>
 using namespace std;
 class Unit{
     int a_;
     char* pch_;
 public:
     Unit(){ cout << "Simple constr" << endl;}
     Unit(int r) : a_(r), pch_(new char[100]){ cout << "Constr " << endl;}
     Unit(const Unit& ){ cout << "Constr copy " << endl;}
     ~Unit(){
         delete [] pch_;
         cout << "Destr" << endl;
     }
 };
 int main(){
     Unit a = 20;
     return 0;
 }

Вызывается только конструктор с параметром и естественно деструктор.

Почему не так: 1. Вызывается конструктор с параметром: Unit(20). 2. Происходит присваивание в объект который не был создан - это копирование. Вызывается конструктор копирования. Как раз rvalue можно передать по const T&. 3. Деструктор для rvalue. 4. Деструктор для a.

Answer 1

В "классическом" С++ (С++98) ваша инициализация копированием (copy-initialization)

Unit a = 20;

действительно концептуально означала именно

Unit a = Unit(20);

с применением конструктора конверсии Unit::Unit(int) и затем конструктора копирования Unit::Unit(const Unit &). Однако даже в "классическом" С++ компилятору было разрешено исключать формирование промежуточного временного объекта и оптимизировать код до прямой инициализации (direct-initialization)

Unit a(20);

т.е. исключать вызов конструктора копирования даже если конструктор копирования обладал побочными эффектами. Эта оптимизация называется copy elision. (Даже при выполнении copy elision наличие доступного конструктора копирования все равно требовалось.)

Начиная с С++17 в языке появилась гарантированная copy elision, при котором ваша инициализация гарантированно трактуется как

Unit a(20);

без требования наличия конструктора копирования.

Т.е. в любом случае, ожидать тут обязательного вызова конструктора копирования вы не должны (и не должны были никогда). В "классическом" С++ конструктор копирования тут мог быть вызван, но не более того.

В вашем конкретном случае инициализация копированием (copy-initialization) ведет себя идентично прямой инициализации (direct-initialization), но в общем случае существенные различия между этими формами инициализации сохраняются и в С++17. Например

struct A {
  A(int) {}
};
struct B {
  operator int() const { return 0; }
};
int main()
{
  B b;
  A a1(b);  // Все в порядке
  A a2 = b; // Ошибка
}

P.S. Никакого "присваивания" тут, конечно, нет и в помине.

Answer 2

Пусть наличие = не вводит в вас в заблуждение, форма записи Unit a = 20; называется copy initialization она полностью аналогична записи Unit a(20);, за тем исключением, что не допускает вызова explicit конструктора.

Начиная с C++11 вместо них следует использовать list initialization Unit a{20};.

Начиная с С++17 добиться вызова конструктора копирования / перемещения при создании временного объекта больше невозможно, т.е.

Unit foo(void) { return Unit{Unit{20}}; }
Unit a{Unit{foo()}};

Приведет к вызову только одного конструктора.

Answer 3

Потому что

Unit a = 20;

это по сути то же, что и

Unit a(20);

просто другая форма записи. Т.е. вызывается конструктор Unit(int r).

А при выходе из main - деструктор.

READ ALSO
QPushButton - подсветка при наведении - не работает в винде

QPushButton - подсветка при наведении - не работает в винде

Для QPushButton задаю иконку с 3мя установленными картинками: Normal, Disabled, ActiveПосле сборки и запуска на linux, всё отрабатывает как ожидается - при наведении...

173
Синхронная обработка запросов через boost::asio

Синхронная обработка запросов через boost::asio

Первая итерация срабатывает превосходно, ввожу строку - получаю ответ, а второй раз как бы "зависает" - те

201
миллион исходящих TCP-соединений [требует правки]

миллион исходящих TCP-соединений [требует правки]

"C++ Windows" миллион исходящих TCP-соединений как обойти ограничени 65535 ?

198
Задействовать нужное кол-во IP адресов

Задействовать нужное кол-во IP адресов

C++ как подключить к программе несколько ipЧто бы расширить локальные порты 2*65535

189