Семантика перемещения не обязательно является большим улучшением, когда вы возвращаете значение - и когда / если вы используете shared_ptr
(или что-то подобное), вы, вероятно, преждевременно пессимизируете. В действительности, почти все достаточно современные компиляторы выполняют то, что называется оптимизацией возвращаемого значения (RVO) и оптимизацией именованного возврата (NRVO). Это означает, что когда вы возвращаете значение, а не копируете его вообщеони просто передают скрытый указатель / ссылку на то, где значение будет назначено после возврата, и функция использует его для создания значения, в котором оно должно закончиться. Стандарт C ++ включает в себя специальные положения, позволяющие это сделать, поэтому даже если (например) ваш конструктор копирования имеет видимые побочные эффекты, нет необходимости использовать конструктор копирования для возврата значения. Например:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
Основная идея здесь довольно проста: создать класс с достаточным количеством контента, который мы бы по возможности избегали копировать ( std::vector
мы заполняем 32767 случайных целых). У нас есть явная копия ctor, которая покажет нам, когда / если она будет скопирована. У нас также есть немного больше кода, чтобы сделать что-то со случайными значениями в объекте, так что оптимизатор не будет (по крайней мере легко) устранять все в классе только потому, что он ничего не делает.
Затем у нас есть некоторый код для возврата одного из этих объектов из функции, а затем мы используем суммирование, чтобы убедиться, что объект действительно создан, а не просто полностью проигнорирован. Когда мы запускаем его, по крайней мере, с самыми последними / современными компиляторами, мы обнаруживаем, что созданный нами конструктор копирования никогда не запускается вообще - и да, я уверен, что даже быстрое копирование с a shared_ptr
по-прежнему медленнее, чем без копирования совсем.
Перемещение позволяет вам делать значительное количество вещей, которые вы просто не можете сделать (напрямую) без них. Рассмотрим часть «слияния» внешнего вида слияния - скажем, у вас есть 8 файлов, которые вы собираетесь объединить вместе. В идеале вы хотели бы поместить все 8 из этих файлов в vector
- но, поскольку vector
( начиная с C ++ 03) необходимо иметь возможность копировать элементы, а ifstream
s не может быть скопировано, вы застряли с некоторымиunique_ptr
/ shared_ptr
, или что-то в этом порядке, чтобы иметь возможность поместить их в вектор. Обратите внимание, что даже если (например) мы reserve
поместим в пробел, vector
так что мы уверены, что наши ifstream
s никогда не будут скопированы, компилятор об этом не узнает, поэтому код не скомпилируется, даже если мы знаем, что конструктор копирования никогда не будет использовал в любом случае.
Хотя это все еще не может быть скопировано, в C ++ 11 ifstream
может быть перемещено. В этом случае объекты, вероятно , никогда не будут перемещены, но тот факт, что они могут быть в случае необходимости, делает компилятор счастливым, поэтому мы можем помещать наши ifstream
объекты vector
напрямую, без каких-либо умных указателей.
Вектор, который действительно расширяется, является довольно приличным примером того времени, когда семантика перемещения действительно может быть / полезна. В этом случае RVO / NRVO не поможет, потому что мы не имеем дело с возвращаемым значением из функции (или чем-то очень похожим). У нас есть один вектор, содержащий несколько объектов, и мы хотим переместить эти объекты в новый, больший кусок памяти.
В C ++ 03 это было сделано путем создания копий объектов в новой памяти, а затем уничтожения старых объектов в старой памяти. Однако сделать все эти копии просто для того, чтобы выбросить старые, было пустой тратой времени. В C ++ 11 вы можете ожидать их перемещения. Как правило, это позволяет нам, по сути, делать поверхностную копию вместо (как правило, намного более медленной) глубокой копии. Другими словами, со строкой или вектором (только для пары примеров) мы просто копируем указатель (и) в объектах, а не копируем все данные, на которые ссылаются эти указатели.
shared_ptr
просто ради быстрого копирования), и если семантика перемещения может достичь того же самого, практически без штрафа за кодирование, семантику и чистоту.