Урок №200. Функція std::move()

  Оновл. 8 Жов 2021  | 

 1267

Як тільки ви почнете регулярно використовувати семантику переміщення, ви зрозумієте наскільки корисною вона може бути.

Проблема

Дуже часто об’єкти, з якими вам доведеться працювати, будуть не 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()


Функція 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 Зірок (46 оцінок, середня: 4,98 з 5)
Завантаження...

Залишити відповідь

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