C++ семантика переміщення та rvalue посилання є ключовими для ефективного управління ресурсами. У цьому блозі ми розглянемо ці просунуті можливості крок за кроком. Наприкінці ви отримаєте чітке уявлення про те, як їх ефективно використовувати. Давайте зануримось у тему! 🌟
🔄 Rvalue посилання та перевантаження функцій
У C++ ви можете перевантажувати функції залежно від того, чи є аргументи lvalue чи rvalue. Ось як це робиться:
Приклад: Перевантажені функції
#include
#include
void process(const int& x) {
std::cout << "Викликано перевантаження посилання на lvalue\n";
}
void process(int&& x) {
std::cout << "Викликано перевантаження посилання на rvalue\n";
}
int main() {
int a = 42;
process(a); // Передача lvalue
process(std::move(a)); // Передача rvalue (через std::move)
process(100); // Передача rvalue (літерал)
return 0;
}
Вивід:
Викликано перевантаження посилання на lvalue
Викликано перевантаження посилання на rvalue
Викликано перевантаження посилання на rvalue
🧩 Ключові спостереження:
- Перевантаження для посилання на lvalue викликається для іменованих змінних (наприклад,
a
). - Перевантаження для посилання на rvalue викликається для тимчасових об'єктів або коли lvalue перетворюється в rvalue за допомогою
std::move
.
🧳 Що таке типи "тільки для переміщення"?
Тип "тільки для переміщення" — це клас, який дозволяє виконувати операції переміщення (передача ресурсів), але не копіювання. Найвідоміший приклад — це std::unique_ptr
, який передає право власності на ресурс, але не може бути скопійований.
Навіщо використовувати типи "тільки для переміщення"?
- Вони запобігають дублюванню ресурсів.
- Забезпечують єдиного власника для динамічно виділених ресурсів, уникнення подвійного видалення або висячих вказівників.
🛠️ Класи, що підтримують переміщення
Для того, щоб увімкнути семантику переміщення, клас повинен визначити конструктор переміщення та оператор присвоєння переміщення. Це дозволяє передавати ресурси з одного об'єкта в інший без копіювання.
Прототипи для операторів переміщення:
class Test {
public:
Test(Test&& other) noexcept; // Конструктор переміщення
Test& operator=(Test&& other) noexcept; // Оператор присвоєння переміщення
};
🚫 delete проти default
Ключові слова delete
та default
контролюють спеціальні члени функцій:
delete
: Вимикає функцію, запобігаючи її використанню.default
: Запитує компілятору створити стандартну реалізацію.
Приклад:
class Test {
public:
Test(const Test&) = delete; // Конструктор копіювання вимкнений
Test& operator=(const Test&) = delete; // Оператор присвоєння копіювання вимкнений
Test(Test&&) = default; // Стандартний конструктор переміщення
Test& operator=(Test&&) = default; // Стандартний оператор присвоєння переміщення
};
💡 Клас, який можна переміщувати, але не копіювати
Давайте створимо клас, щоб продемонструвати ці концепції:
Код:
#include
#include
class MoveOnly {
public:
MoveOnly() = default;
MoveOnly(const MoveOnly&) = delete; // Вимкнено конструктор копіювання
MoveOnly& operator=(const MoveOnly&) = delete; // Вимкнено оператор присвоєння копіювання
MoveOnly(MoveOnly&&) noexcept = default; // Увімкнено конструктор переміщення
MoveOnly& operator=(MoveOnly&&) noexcept = default; // Увімкнено оператор присвоєння переміщення
};
int main() {
MoveOnly obj1;
MoveOnly obj2 = std::move(obj1); // Переміщення дозволено
// MoveOnly obj3 = obj1; // Помилка: Копіювання вимкнене
return 0;
}
📤 Передавати за значенням… чи переміщувати?
Передача об'єктів за значенням може призвести до непотрібних копій.
Давайте розглянемо різницю між передачею за значенням і передачею за допомогою переміщення.
Приклад: Передача за значенням
#include
#include
#include
void processVector(std::vector vec) {
std::cout << "Розмір вектора всередині функції: " << vec.size() << "\n";
}
int main() {
std::vector words{"Hello", "World", "C++"};
std::cout << "До виклику функції: " << words.size() << "\n";
processVector(words); // Створюється копія
std::cout << "Після виклику функції: " << words.size() << "\n";
return 0;
}
Вивід:
До виклику функції: 3
Розмір вектора всередині функції: 3
Після виклику функції: 3
Вектор залишається незмінним, оскільки була передана копія.
Використання std::move
для передачі за допомогою переміщення
int main() {
std::vector words{"Hello", "World", "C++"};
std::coutn";
processVector(std::move(words)); // Передача права власності
std::cout << "Після виклику функції: " << words.size() << "\n";
return 0;
}
Вивід:
До виклику функції: 3
Розмір вектора всередині функції: 3
Після виклику функції: 0
🧩 Ключові спостереження:
- Без
std::move
: Оригінальний вектор не змінюється, оскільки створюється копія. - З
std::move
: Право власності на вектор передається функції, залишаючи оригінальний вектор порожнім.
🌟 Підсумки
Розуміння rvalue посилань, семантики переміщення та таких просунутих технік, як std::move
, дає вам змогу писати більш ефективний і виразний сучасний C++ код. Ці інструменти є основою управління ресурсами в сучасному програмуванні на C++.
by : Malinda Gamage
Перекладено з: 🚀 Modern C++: Mastering Rvalue References, Move Semantics, and Advanced Techniques 💡