🚀 Сучасний C++: Оволодіння Rvalue посиланнями, семантикою переміщення та передовими техніками 💡

C++ семантика переміщення та rvalue посилання є ключовими для ефективного управління ресурсами. У цьому блозі ми розглянемо ці просунуті можливості крок за кроком. Наприкінці ви отримаєте чітке уявлення про те, як їх ефективно використовувати. Давайте зануримось у тему! 🌟

pic

🔄 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

🧩 Ключові спостереження:

  1. Перевантаження для посилання на lvalue викликається для іменованих змінних (наприклад, a).
  2. Перевантаження для посилання на rvalue викликається для тимчасових об'єктів або коли lvalue перетворюється в rvalue за допомогою std::move.

🧳 Що таке типи "тільки для переміщення"?

Тип "тільки для переміщення" — це клас, який дозволяє виконувати операції переміщення (передача ресурсів), але не копіювання. Найвідоміший приклад — це std::unique_ptr, який передає право власності на ресурс, але не може бути скопійований.

Навіщо використовувати типи "тільки для переміщення"?

  • Вони запобігають дублюванню ресурсів.
  • Забезпечують єдиного власника для динамічно виділених ресурсів, уникнення подвійного видалення або висячих вказівників.

🛠️ Класи, що підтримують переміщення

Для того, щоб увімкнути семантику переміщення, клас повинен визначити конструктор переміщення та оператор присвоєння переміщення. Це дозволяє передавати ресурси з одного об'єкта в інший без копіювання.

Прототипи для операторів переміщення:

class Test {  
public:  
 Test(Test&& other) noexcept; // Конструктор переміщення  
 Test& operator=(Test&& other) noexcept; // Оператор присвоєння переміщення  
};

🚫 delete проти default

Ключові слова delete та default контролюють спеціальні члени функцій:

  1. delete: Вимикає функцію, запобігаючи її використанню.
  2. 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

🧩 Ключові спостереження:

  1. Без std::move: Оригінальний вектор не змінюється, оскільки створюється копія.
  2. З std::move: Право власності на вектор передається функції, залишаючи оригінальний вектор порожнім.

🌟 Підсумки

Розуміння rvalue посилань, семантики переміщення та таких просунутих технік, як std::move, дає вам змогу писати більш ефективний і виразний сучасний C++ код. Ці інструменти є основою управління ресурсами в сучасному програмуванні на C++.

by : Malinda Gamage

Перекладено з: 🚀 Modern C++: Mastering Rvalue References, Move Semantics, and Advanced Techniques 💡

Leave a Reply

Your email address will not be published. Required fields are marked *