Сборка мусора и создание Actor на C++

Урок про управление памятью в Unreal и про то, как написать свой Actor с компонентами на C++.

Сборка мусора (Garbage Collection) — это автоматическое удаление объектов, на которые больше никто не ссылается, чтобы освободить память.

Память без ручного delete

В обычном C++ за память отвечает программист: что выделил через new, то и удаляет через delete. Это источник утечек и крашей. Unreal убирает эту боль для своих объектов (наследников UObject, включая все Actor'ы): у него есть сборщик мусора, как в C# или Java. Вы не вызываете delete для Actor'ов — движок сам удалит их, когда на них не останется ссылок.

Почему UPROPERTY критичен для памяти

Сборщик мусора находит «живые» объекты, проходя по ссылкам, помеченным UPROPERTY. Если вы храните указатель на UObject без UPROPERTY, сборщик его «не видит» и может удалить объект, хотя он вам ещё нужен. Поэтому правило: любые указатели на UObject-объекты, которые должны жить, помечайте UPROPERTY.

Сборщик идёт по UPROPERTY-ссылкам:
[GameMode] --UPROPERTY--> [Player] --UPROPERTY--> [Weapon]   = живые

[Orphan]  (нет ссылок) ====> удаляется при следующей сборке

Создание компонентов в конструкторе

Компоненты Actor'а создают в конструкторе через специальную функцию. Она и регистрирует компонент в системе рефлексии.

AHero::AHero()
{
    PrimaryActorTick.bCanEverTick = true;

    // Создаём меш как корневой компонент
    MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    RootComponent = MeshComp;
}

Указатель MeshComp при этом объявлен с UPROPERTY в заголовке — иначе сборщик мусора удалит компонент.

Спавн Actor'а во время игры

Чтобы создать Actor по ходу игры (например, пулю при выстреле), используют SpawnActor. Ему передают класс, позицию и поворот.

FActorSpawnParameters Params;
ABullet* Bullet = GetWorld()->SpawnActor<ABullet>(
    BulletClass, SpawnLocation, SpawnRotation, Params);

Как работает под капотом

Сборщик запускается периодически. Он начинает с «корней» (например, GameInstance) и обходит граф UPROPERTY-ссылок, помечая достижимые объекты живыми. Всё, до чего не дошёл, считается мусором и удаляется. Это называется алгоритм mark-and-sweep. Поскольку сборка идёт не каждый кадр, объекты не исчезают мгновенно после потери ссылок — между потерей и удалением проходит время.

Частые ошибки

  • Вызывать delete для Actor'а. Этим управляет движок; для удаления используйте Destroy().
  • Указатель на UObject без UPROPERTY. Объект внезапно исчезнет — классический баг.
  • Создавать компоненты вне конструктора через new. Используйте CreateDefaultSubobject в конструкторе.

Умные указатели и слабые ссылки

Не всё в Unreal — это UObject под защитой сборщика. Для обычных C++-объектов есть умные указатели: TSharedPtr считает ссылки и удаляет объект, когда их не осталось, а TUniquePtr владеет объектом единолично. Для UObject'ов, на которые нужна «необязательная» ссылка, существует TWeakObjectPtr — слабый указатель: он не мешает сборщику удалить объект, но позволяет безопасно проверить, жив ли тот ещё. Это спасает от висячих ссылок: вместо краша вы получаете честный ответ «объект уже уничтожен».

Выбор инструмента зависит от задачи. Сильную ссылку на UObject, который должен жить, делают через UPROPERTY. Если же объект может исчезнуть в любой момент и вы лишь хотите им воспользоваться, когда он есть, — TWeakObjectPtr безопаснее. Понимание этих вариантов отличает аккуратный код от мин замедленного действия.

Итоги

  • Unreal сам управляет памятью UObject через сборщик мусора — delete не нужен.
  • Указатели на UObject помечайте UPROPERTY, иначе сборщик их удалит.
  • Компоненты создают в конструкторе через CreateDefaultSubobject.
  • Actor'ы во время игры создают через SpawnActor, а удаляют через Destroy().
Проверьте себя
1. Как в Unreal удаляются Actor'ы?
AПрограммист вызывает delete вручную
BИх удаляет сборщик мусора, когда не остаётся ссылок
CОни никогда не удаляются
DИх удаляет операционная система
2. Зачем помечать указатель на UObject макросом UPROPERTY?
AДля скорости
BЧтобы сборщик мусора видел ссылку и не удалил нужный объект
CЧтобы уменьшить память
DЭто необязательно
3. Какой функцией создают компоненты в конструкторе Actor'а?
Anew
BCreateDefaultSubobject
CSpawnActor
Dmalloc