Переместить семантику и скопировать конструктор

Я написал программу, как показано ниже:

#include <iostream>

using namespace std;

class A {
public:
    A() {
    }
    A(A &a) {
        id = a.id;
        cout << "copy constructor" << endl;
    }
    A& operator=(A &other) {
        id = other.id;
        cout << "copy assignment" << endl;
        return *this;
    }
    A(A &&other) {
        id = other.id;
        cout << "move constructor" << endl;
    }
    A& operator=(A &&other) {
        id = other.id;
        cout << "move assignment" << endl;
        return *this;
    }
public:
    int id = 10;
};

A foo() {
    A a;
    return a;
}

int main()
{
    A a;
    A a2(a); // output: copy constructor
    A a3 = a2; // output: copy constructor
    a3 = a2; // output: copy assignment
    A a4 = foo(); // output: 
    a4 = foo(); // output: move assignment
    return 0;
}

Я скомпилировал его на своей Mac OS. Результат:

copy constructor
copy constructor
copy assignment
move assignment

Мой вопрос:

  1. Почему вывод A a4 = foo(); пуст? Я думал, что он должен вызвать конструктор перемещения.
  2. Почему вывод A a3 = a2; равен copy constructor вместо copy assignment?

person injoy    schedule 27.10.2013    source источник


Ответы (2)


  1. Потому что копии и перемещения могут быть исключены компилятором, если он того пожелает. Соответствующий конструктор все еще должен существовать, но в стандарте явно указано, что его нельзя вызывать. (Это редкий пример оптимизации, определяемой стандартом, в частности, позволяющей также определять стандартом оптимизацию возвращаемого значения.)

  2. Поскольку использование = при инициализации выполняет построение, а не присваивание. Это несколько сбивает с толку, что это синтаксис. A a3(a2), который [по существу] эквивалентен, был бы более ясным в этом отношении.

person Lightness Races in Orbit    schedule 27.10.2013
comment
Спасибо за ваш ответ. Но что касается первого пункта, я немного запутался. Если конструктор перемещения не может быть вызван, то как мы можем правильно инициализировать l-значение, как ожидалось? Например, если я перепишу конструктор перемещения как id = other.id * 10;, если он был пропущен, как получить правильный идентификатор для a4? - person injoy; 27.10.2013
comment
@injoy: Ну, конструктор перемещения должен быть написан не так. - person Lightness Races in Orbit; 27.10.2013
comment
Да, ты прав. Кстати, я добавил одну строку cout << "default constructor" << endl; в файл A(){}. И теперь выход A a4 = foo(); равен двум default constructor. Похоже, move constructor не звонили. Почему? - person injoy; 27.10.2013
comment
Потому что, как я уже сказал, в этом нет необходимости. Временное можно использовать для непосредственного создания именованного объекта, исключая возможность копирования/перемещения. - person Lightness Races in Orbit; 27.10.2013
comment
Тогда как мы можем заставить компилятор вызвать конструктор перемещения? Потому что иногда, если в классе есть указатель, конструктор перемещения был бы намного эффективнее. - person injoy; 27.10.2013
comment
Отказ от вызова копирующего или перемещающего звена не так эффективен, как их игнорирование. Не заставляйте компилятор что-либо делать: он знает, что делает. - person Lightness Races in Orbit; 27.10.2013

Компилятор генерирует методы по умолчанию для:

A (const A &);
A & operator = (const A &);

Если вы добавите квалификатор const к своим методам копирования / назначения, вы можете увидеть ожидаемые результаты.

person Brett Hale    schedule 27.10.2013