<MyRusakov.ru />

Создание игр на Unreal Engine 5

Создание игр на Unreal Engine 5

Данный курс научит Вас созданию игр на Unreal Engine 5. Курс состоит из 12 модулей, в которых Вы с нуля освоите этот движок и сможете создавать самые разные игры.

В курсе Вы получите всю необходимую теоретическую часть, а также увидите массу практических примеров. Дополнительно, почти к каждому уроку идут упражнения для закрепления материала.

Помимо самого курса Вас ждёт ещё 8 бесплатных ценных Бонусов: «Chaos Destruction», «Разработка 2D-игры», «Динамическая смена дня и ночи», «Создание динамической погоды», «Создание искусственного интеллекта для NPC», «Создание игры под мобильные устройства», «Создание прототипа RPG с открытым миром» и и весь курс «Создание игр на Unreal Engine 4» (актуальный и в 5-й версии), включающий в себя ещё десятки часов видеоуроков.

Подробнее
Подписка

Подпишитесь на мой канал на YouTube, где я регулярно публикую новые видео.

YouTube Подписаться

Подписавшись по E-mail, Вы будете получать уведомления о новых статьях.

Подписка Подписаться

Добавляйтесь ко мне в друзья ВКонтакте! Отзывы о сайте и обо мне оставляйте в моей группе.

Мой аккаунт Мой аккаунт Моя группа
Опрос

Какая тема Вас интересует больше?

Семантика перемещения в C++: std::move, rvalue‑ссылки и правило пяти — практическое руководство

Семантика перемещения в C++: std::move, rvalue‑ссылки и правило пяти — практическое руководство

Запрос, который часто вводят в поиск: «семантика перемещения c++ std::move примеры». Это руководство закрывает его полностью. Ниже — простые объяснения, много кода и практические советы, чтобы вы уверенно использовали перемещение в реальных проектах.

Что такое семантика перемещения в C++

Идея проста: вместо дорогого копирования ресурсов (памяти, файловых дескрипторов и т.д.) мы «передаём» владение ими другому объекту. Основа механики — rvalue‑ссылки (T&&) и std::move. В отличие от копии, перемещение делает старый объект «пустым», а новый — владельцем ресурса.

  • Копирование: создаёт второй независимый ресурс.
  • Перемещение: передаёт ресурс без дублирования, быстро.
  • std::move: не двигает сам по себе, а лишь превращает выражение в rvalue, позволяя вызвать move‑конструктор или move‑присваивание.

Практический пример: класс с ресурсом + логи

Сделаем небольшой класс Buffer, который владеет динамическим массивом. Добавим копирующие и перемещающие операции, а также логи, чтобы видеть, что именно вызвалось.

#include <iostream>
#include <vector>
#include <algorithm>
#include <utility>

class Buffer {
    std::size_t size_ = 0;
    int* data_ = nullptr;
public:
    Buffer() = default;
    explicit Buffer(std::size_t n) : size_(n), data_(n ? new int[n]{} : nullptr) {
        std::cout << "Ctor: size=" << size_ << '\n';
    }
    ~Buffer() {
        delete[] data_;
        std::cout << "Dtor: size=" << size_ << '\n';
    }

    Buffer(const Buffer& other) : size_(other.size_), data_(other.size_ ? new int[other.size_] : nullptr) {
        std::cout << "Copy ctor\n";
        if (data_) std::copy(other.data_, other.data_ + size_, data_);
    }
    Buffer& operator=(const Buffer& other) {
        std::cout << "Copy assign\n";
        if (this == &other) return *this;
        Buffer tmp(other); // копия
        swap(tmp);
        return *this;
    }

    Buffer(Buffer&& other) noexcept : size_(std::exchange(other.size_, 0)),
                                       data_(std::exchange(other.data_, nullptr)) {
        std::cout << "Move ctor\n";
    }
    Buffer& operator=(Buffer&& other) noexcept {
        std::cout << "Move assign\n";
        if (this == &other) return *this;
        delete[] data_;
        size_ = std::exchange(other.size_, 0);
        data_ = std::exchange(other.data_, nullptr);
        return *this;
    }

    void swap(Buffer& other) noexcept {
        std::swap(size_, other.size_);
        std::swap(data_, other.data_);
    }
    std::size_t size() const noexcept { return size_; }
};

int main() {
    Buffer a(1'000'000);           // большой буфер
    std::vector<Buffer> v;
    v.reserve(3);

    v.push_back(a);                // КОПИЯ
    v.push_back(std::move(a));     // ПЕРЕМЕЩЕНИЕ, a становится пустым
    v.emplace_back(500'000);       // Конструирование на месте, без лишних копий

    std::cout << "a.size=" << a.size() << '\n'; // обычно 0 после перемещения
}

Скомпилируйте и посмотрите вывод. Вы увидите «Copy ctor/assign» и «Move ctor/assign» в разных местах. Это лучший способ прочувствовать механику на практике.

Когда и как правильно использовать std::move

  • Вы передаёте временный или более не нужный объект: std::move(x).
  • Выдача из контейнеров: v.push_back(std::move(elem)).
  • Реализация swap и операторов присваивания по перемещению — обязательно помечайте их noexcept.
  • Возврат из функции: return x; обычно и так переместит (NRVO/RVO), явный std::move в return чаще не нужен и может мешать оптимизациям копирования.

Типичные ошибки и как их исправить

1) Использование объекта после перемещения

Buffer b(10);
Buffer c = std::move(b);
// b в валидном, но пустом состоянии. Полагаться на старое содержимое нельзя.
std::cout << b.size() << '\n'; // допустимо, но здесь скорее всего 0

Совет: сразу переиспользуйте объект в новом качестве (переинициализируйте) или не трогайте его вовсе.

2) std::move от const-объекта — это копия, а не перемещение

const Buffer c(42);
std::vector<Buffer> v;
v.push_back(std::move(c)); // вызовется КОПИЯ, т.к. move-ctor: Buffer(Buffer&&), а не const Buffer&&

Совет: перемещать имеет смысл из неконстантных объектов. Если объект должен быть перемещаемым, не делайте его const в той точке, где планируете перемещать.

3) Нет noexcept у move — контейнеры будут копировать

Стандартные контейнеры при перераспределении памяти предпочитают перемещать элементы, но только если move-конструктор помечен noexcept. Иначе для безопасности они используют копию.

struct X {
    X() = default;
    X(X&&) noexcept { /* ... */ } // Добавьте noexcept!
};

4) std::move не двигает сам по себе

Это всего лишь «каст» к rvalue. Двигать будет ваш move‑конструктор/оператор, если они определены и доступны. Без них — будет копия или ошибка компиляции.

Коротко о std::forward и perfect forwarding

Если вы пишете шаблонные обёртки/фабрики и хотите «пробрасывать» аргументы без лишних копий, используйте forwarding-ссылки и std::forward.

#include <utility>

template <class F, class... Args>
auto call(F&& f, Args&&... args) {
    return std::forward<F>(f)(std::forward<Args>(args)...);
}

Здесь lvalue-аргументы останутся lvalue, rvalue — rvalue. Это не про ускорение само по себе, а про сохранение «категории значения», чтобы в глубине стека вызовов сработало именно перемещение там, где это возможно.

Лучшие практики: правило пяти и не только

  • Если ваш тип управляет ресурсом, реализуйте «правило пяти»: деструктор, копирующий конструктор/присваивание, перемещающий конструктор/присваивание.
  • Там, где возможно, используйте =default: часто компилятор сгенерирует корректный move, если все члены перемещаемые.
  • Если копирование не имеет смысла — пометьте его как =delete и оставьте только перемещение (move-only тип).
  • Внутри move-операций используйте std::exchange для аккуратной передачи владения и обнуления исходника.
  • Пишите swap noexcept и используйте идиому копирования-с-последующим-swap в копирующем присваивании (как в примере).
  • Профилируйте: не везде перемещение критично. Иногда NRVO уже всё оптимизирует без вашего вмешательства.

Немного про взаимодействие с контейнерами

  • emplace_back конструирует объект на месте — это часто быстрее push_back временного объекта.
  • При росте vector элементы переносятся: с noexcept move это будут перемещения, иначе — копии.
  • Храните перемещаемые типы «как есть» (по значению). Не оборачивайте их без необходимости, чтобы не потерять преимущества перемещения.

Чек‑лист перед ревью кода

  • Есть ли у ресурсоёмких типов корректные move‑операции и помечены ли они noexcept?
  • Не делаю ли std::move из const?
  • Не использую ли данные из объекта после перемещения?
  • Можно ли заменить push_back(expr) на emplace_back(args...) для избежания временных объектов?
  • Не мешаю ли return x; сработать NRVO, ставя лишний std::move?

Хотите системно закрыть пробелы в базовых и продвинутых темах C++ с практикой и домашними заданиями? Посмотрите программу и первые уроки в курсе «Программирование на C++ с Нуля до Гуру» — начать обучение и прокачать C++ сейчас.

Итоги

Семантика перемещения — один из самых ощутимых «бонусов» современного C++. Освойте rvalue‑ссылки, std::move и правило пяти, помечайте move‑операции noexcept, избегайте распространённых ловушек — и вы получите быстрый, аккуратный и предсказуемый код. Используйте приведённый пример как шаблон и смело внедряйте перемещение в свои проекты.

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (https://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: https://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: https://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

  1. Кнопка:

    Она выглядит вот так: Как создать свой сайт

  2. Текстовая ссылка:

    Она выглядит вот так: Как создать свой сайт

  3. BB-код ссылки для форумов (например, можете поставить её в подписи):

Комментарии (0):

Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.