//
В первом случае оператору присвоения передается объект v2. Его типом является StrVec, а выражение v2 является l-значением. Версия присвоения при перемещении не является подходящей (см. раздел 6.6), поскольку нельзя неявно связать ссылку на r-значение с l-значением. Следовательно, в этом случае используется оператор присвоения копии.
Во втором случае присваивается результат вызова функции getVec(), — это r-значение. Теперь подходящими являются оба оператора присвоения — результат вызова функции getVec() можно связать с любым параметром оператора. Вызов оператора присвоения копии требует преобразования в константу, в то время как StrVec&& обеспечивает точное соответствие. Следовательно, второе присвоение использует оператор присваивания при перемещении.
Что если класс имеет конструктор копий, но не определяет конструктор перемещения? В данном случае компилятор не будет синтезировать конструктор перемещения. Это значит, что у класса есть конструктор копий, но нет конструктора перемещения. Если у класса нет конструктора перемещения, подбор функции гарантирует, что объекты этого типа будут копироваться, даже при попытке перемещения их вызовом функции move():
class Foo {
public:
Foo() = default;
Foo(const Foo&); //
//
};
Foo x;
Foo y(x); //
Foo z(std::move(x)); //
//
Вызов функции move(x) при инициализации объекта z возвращает указатель Foo&&, привязанный к объекту x. Конструктор копий для класса Foo является подходящим, поскольку вполне допустимо преобразовать Foo&& в const Foo&. Таким образом, инициализация объекта z использует конструктор копий класса Foo.
Следует заметить, что использование конструктора копий вместо конструктора перемещения почти безусловно безопасно (то же справедливо и для оператора присвоения). Обычно конструктор копий отвечает требованиям соответствующего конструктора перемещения: он копирует заданный объект и оставляет оригинальный объект в допустимом состоянии. Конструктор копий, напротив, не будет изменять значение оригинального объекта.
Версия класса HasPtr, определявшая
class HasPtr {
public:
//
HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) {p.ps = 0;}
//
HasPtr& operator=(HasPtr rhs)
{ swap(*this, rhs); return *this; }
//
};
В этой версии класса добавлен конструктор перемещения, получающий значения из своего аргумента. Тело конструктора обнуляет указатель-член данного объекта класса HasPtr, чтобы гарантировать безопасное удаление оригинального объекта перемещения. Эта функция не делает ничего, она не может передать исключение, поэтому отметим ее как noexcept (см. раздел 13.6.2).
Теперь рассмотрим оператор присвоения. У него есть не ссылочный параметр, а значит, этот параметр инициализируется копией (см. раздел 13.1.1). В зависимости от типа аргумента инициализация копией использует либо конструктор копий, либо конструктор перемещения; l-значения копируются, а r-значения перемещаются. В результате этот оператор однократного присвоения действует и как присвоение копии, и как присваивание при перемещении.
Предположим, например, что объекты hp и hp2 являются объектами класса HasPtr:
hp = hp2; //
//
hp = std::move(hp2); //