Урок №192. std::move

  Юрий  | 

    | 

  Обновл. 29 Дек 2018  | 

 1066

Как только вы начнете регулярнее использовать семантику перемещения, вы обнаружите насколько полезной она может быть. Но очень часто объекты, с которыми вам придется работать, будут не r-values, а l-values. Например, рассмотрим следующий шаблон функции swap():

Принимая два объекта типа T (в данном случае std::string), функция swap() меняет местами их значения, делая при этом три копии. Следовательно, результат выполнения программы выше:

x: Anton
y: Max
x: Max
y: Anton

А как мы уже знаем из предыдущего урока, копирование – это не очень эффективно. А поскольку мы выполняем копирование трижды, то это ещё и сравнительно медленно.

Мы можем этого избежать. Наша цель – поменять местами значения x и y. А для этого мы можем использовать перемещение, вместо копирования, сделав наш код более производительным!

Но как? Проблема состоит в том, что параметры x и y являются ссылками l-value, а не ссылками r-value, поэтому у нас нет способа вызвать конструктор перемещения или оператор присваивания перемещением вместо конструктора копирования и оператора присваивания копированием. По умолчанию у нас семантика копирования. Что делать?

std::move

В C++11 std::move — это стандартная библиотечная функция, которая конвертирует передаваемый аргумент в r-value. Мы можем передать l-value в std::move, и std::move вернет нам ссылку r-value. Для работы с std::move нужно подключить заголовочный файл utility.

Вот та же программа, что выше, но с функцией swap(), которая использует std::move для преобразования l-values в r-values, чтобы мы имели возможность использовать семантику перемещения вместо семантики копирования:

Результат выполнения один и тот же:

x: Anton
y: Max
x: Max
y: Anton

Но эта версия программы гораздо эффективнее. При инициализации tmp, вместо создания копии x мы используем std::move для конвертации переменной x, которая является l-value, в r-value. А поскольку параметром становится r-value, то, с помощью семантики перемещения, x перемещается в tmp.

Затем, спустя несколько дополнительных перестановок, значение переменной x перемещается в переменную y, а значение y перемещается в x.

Ещё один пример


Мы также можем использовать std::move для заполнения контейнерных классов (таких как std::vector) значениями l-values.

В следующей программе мы сначала добавляем элемент в вектор, используя семантику копирования, а затем добавляем элемент в вектор, используя семантику перемещения:

Результат выполнения программы выше:

Copying str
str: Bye
vector: Bye

Moving str
str:
vector: Bye Bye

В первом случае мы передаём l-value в push_back(), поэтому используется семантика копирования для добавления элемента в вектор. По этой причине переменная str остается с прежним значением.

Во втором случае мы передаём r-value (фактически l-value, которое конвертируется в r-value через std::move) в push_back(), поэтому используется семантика перемещения для добавления элемента в вектор. Это более эффективно, так как элемент вектора может украсть значение переменной std::string, вместо его копирования. По этой же причине, str лишается своего значения.

На этом этапе стоит сообщить, что std::move() как бы подсказывает компилятору, что нам больше не нужен этот объект (по крайней мере, в его текущем состоянии). Следовательно, вы не должны использовать std::move() с любым «постоянным» объектом, который вы не хотите изменять, и вам не следует ожидать, что объекты, которые используются с std::move(), останутся прежними.

Функции перемещения всегда должны оставлять обе стороны в четко определенном состоянии

Как мы уже говорили в предыдущем уроке, вы должны оставлять объекты, ресурсы которых вы перемещаете, в четко определенном состоянии. В идеале это должно быть «нулевое состояние» (null/nullptr).

Теперь мы можем поговорить о том, почему при использовании std::move объект, ресурсы которого перемещаются, может не быть временным. Дело в том, что пользователь может захотеть повторно использовать этот же (теперь пустой) объект или протестировать его каким-либо образом.

В примере выше строка str остается пустой после выполнения перемещения (что всегда делает std::string после успешного перемещения). Таким образом мы можем повторно её использовать, если, конечно, захотим или проигнорировать, если она нам больше не нужна.

Чем ещё полезен std::move?


std::move также может быть полезен при сортировке элементов массива. Многие алгоритмы сортировки (такие как «метод выбора» и «сортировка пузырьком») работают путем замены целых пар элементов. В предыдущих уроках нам приходилось использовать семантику копирования для выполнения таких замен. Теперь же мы можем использовать семантику перемещения, которая более эффективна.

std::move также может быть полезен при перемещении содержимого из одного умного указателя в другой.

Итого

std::move может использоваться всякий раз, когда нужно обрабатывать l-value как r-value с целью использования семантики перемещения вместо семантики копирования.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (9 оценок, среднее: 5,00 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *