Кратко:

Семантика перемещения (move semantics) — это механизм, позволяющий передавать ресурсы от одного объекта к другому без затрат на их копирование. Она особенно полезна в шаблонных функциях и классах, где важно оптимизировать работу с временными объектами.

Основные свойства:

  • Rvalue-ссылки: используются для захвата временных объектов.
  • std::move: преобразует lvalue в rvalue, позволяя вызвать конструктор или оператор перемещения.
  • Конструкторы и операторы перемещения: определяют, как объект должен переместить свои ресурсы.

Примеры кода

Пример функции с семантикой перемещения

#include <utility>
#include <vector>
#include <iostream>
 
template <typename T>
void printVector(const std::vector<T>& vec) {
    for (const T& element : vec) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
}
 
template<typename T>
void processVector(std::vector<T>&& vec) {
    std::vector<T> localVec = std::move(vec);  // Перемещаем ресурсы
    // Используем localVec
    std::cout << "Vector size: " << localVec.size() << std::endl;
}
 
int main() {
    std::vector<int> myVec = {1, 2, 3, 4, 5};
    
    printVector(myVec); // Выводится вектор
 
    processVector(std::move(myVec));  // Передаем временный объект
 
    printVector(myVec); // Выводится пустота
 
    return 0;
}

Пример класса с семантикой перемещения

#include <iostream>
#include <utility>
 
template<typename T>
class MyContainer {
public:
    T* data;
    size_t size;
 
    // Конструктор
    MyContainer(size_t s) : size(s), data(new T[s]) {}
 
    // Деструктор
    ~MyContainer() { delete[] data; }
 
    // Конструктор перемещения. noexcept сообщает компилятору, что конструктор никогда не выбросит исключения
    MyContainer(MyContainer&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
 
    // Оператор перемещения
    MyContainer& operator=(MyContainer&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
 
    // Запрещаем копирование
    MyContainer(const MyContainer&) = delete;
    MyContainer& operator=(const MyContainer&) = delete;
};
 
int main() {
    MyContainer<int> container1(10);
    MyContainer<int> container2 = std::move(container1);  // Перемещаем ресурсы
    return 0;
}

Особенности и рекомендации

  • Используйте std::move только тогда, когда уверены, что больше не будете использовать исходный объект.
  • Определяйте конструкторы и операторы перемещения для классов, работающих с динамическими ресурсами.
  • Сочетайте семантику перемещения с perfect forwarding для максимально эффективного кода в шаблонах.

Пример perfect forwarding

#include <utility>
#include <iostream>
 
template<typename T, typename Arg>
std::shared_ptr<T> create(Arg&& arg) {
    return std::make_shared<T>(std::forward<Arg>(arg));
}
 
class MyClass {
public:
    MyClass(int x) { std::cout << "Constructed with int: " << x << std::endl; }
    MyClass(const std::string& str) { std::cout << "Constructed with string: " << str << std::endl; }
};
 
int main() {
    auto obj1 = create<MyClass>(42);  // Передаем int
    auto obj2 = create<MyClass>(std::string("Hello"));  // Передаем string
    return 0;
}