[Unreal Engine 5] 언리얼 엔진 5의 스마트 포인터

2026. 1. 8. 21:45·프로그래밍/Unreal Engine 5

언리얼 엔진은 표준 C++(std::shared_ptr 등) 대신 자체적인 스마트 포인터 라이브러리를 제공한다. 이를 통해 엔진의 메모리 할당자, 통계 시스템, 그리고 스레드 안전성 모델과 완벽하게 통합된다.

가장 먼저 이해해야 할 핵심은 "관리 대상이 UObject인가, 일반 C++ 클래스(F-Class)인가?"에 따라 사용하는 포인터가 완전히 다르다는 점이다.


1. 일반 C++ 클래스용 스마트 포인터 (F 클래스 등)

UObject를 상속받지 않는 일반 클래스(예: 데이터 구조체, 서드파티 라이브러리 래퍼, 커스텀 로직 클래스)를 관리할 때 사용한다. 이들은 Garbage Collection(GC)의 대상이 아니므로, 레퍼런스 카운팅을 통해 수명을 관리해야 한다.

TSharedPtr (공유 포인터)

  • 특징: 객체의 소유권을 공유합니다. 레퍼런스 카운트가 0이 되면 객체가 소멸된다.
  • Null 허용: nullptr이 될 수 있다.
  • 사용처: 소유권이 여러 곳에 분산되거나, 객체가 존재하지 않을 수 있는 경우(Optional)에 사용한다.

TSharedRef (공유 레퍼런스)

  • 특징: TSharedPtr와 유사하지만 절대 Null일 수 없다.
  • 강점: 초기화 시 반드시 유효한 객체를 가리켜야 하므로, 사용 시 IsValid()나 nullptr 체크를 할 필요가 없어 성능상 유리하고 코드가 안전해진다.
  • 권장 사항: 가능하다면 항상 TSharedPtr보다 TSharedRef를 우선 사용하십시오. UI 프레임워크인 Slate와 UMG의 C++ 단에서는 TSharedRef가 표준이다.

TWeakPtr (약한 포인터)

  • 특징: 객체를 참조하지만 레퍼런스 카운트를 증가시키지 않다.
  • 사용처: 순환 참조(Circular Dependency)를 방지하거나, 객체의 수명에 관여하지 않고 관찰만 해야 할 때 사용한다. 사용 전 반드시 Pin()을 호출하여 TSharedPtr로 변환 후 유효성을 확인해야 한다.

TUniquePtr (고유 포인터)

  • 특징: 리소스에 대한 유일한 소유권을 가집니다. 복사할 수 없고 이동(Move)만 가능하다.
  • 사용처: 특정 범위 내에서만 객체가 필요하거나, 명시적으로 소유권을 넘겨주는 설계에 적합합니다. 가장 가볍고 빠르다.

2. UObject용 스마트 포인터 (AActor, UComponent 등)

UObject 파생 클래스는 언리얼의 Garbage Collection(GC) 시스템이 메모리를 관리합니다. 따라서 위의 TSharedPtr 등을 직접 사용하면 안 된다.

주의: UObject를 TSharedPtr로 감싸지 마십시오. GC 시스템과 충돌하여 메모리 커럽션이나 크래시가 발생한다.

TWeakObjectPtr

  • 핵심: UObject가 GC에 의해 파괴되었는지 안전하게 확인할 수 있다.
  • 문제 상황: 일반적인 AActor* MyActor 포인터를 들고 있을 때, 해당 액터가 Destroy()되면 MyActor는 댕글링 포인터(Dangling Pointer)가 되어 접근 시 크래시가 발생한다.
  • 해결: TWeakObjectPtr<AActor>를 사용하면 액터가 소멸된 후 .IsValid()가 false를 반환하므로 안전하다.

TStrongObjectPtr

  • 핵심: GC 대상인 객체를 강제로 메모리에 유지하고 싶을 때 사용한다(Root Set에 추가).
  • 사용처: 비동기 로딩 중이거나, UPROPERTY로 참조되지 않는 상황에서 잠시 객체를 살려둬야 할 때 드물게 사용된다.

3. 코드 스니펫 및 사용 예시

  • 일반 클래스 관리와 UObject 안전 참조를 혼합한 예시
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"

// 1. 일반 C++ 클래스 (Not UObject)
class FCombatCalculator
{
public:
    FCombatCalculator() { UE_LOG(LogTemp, Log, TEXT("Calculator Created")); }
    ~FCombatCalculator() { UE_LOG(LogTemp, Log, TEXT("Calculator Destroyed")); }

    float CalculateDamage(float BaseDamage) { return BaseDamage * 2.0f; }
};

// 2. 게임플레이 액터
UCLASS()
class MYGAME_API AMyHero : public AActor
{
    GENERATED_BODY()

public:
    AMyHero();

protected:
    virtual void BeginPlay() override;

public:
    // 일반 클래스는 TSharedPtr/Ref로 관리
    // 초기화가 보장된다면 TSharedRef가 좋으나, 멤버 변수는 보통 TSharedPtr로 시작하여 생성자에서 할당
    TSharedPtr<FCombatCalculator> CombatCalc;

    // 다른 액터를 안전하게 참조하기 위해 TWeakObjectPtr 사용
    // UPROPERTY()를 쓰지 않는 경우, GC를 막지 않으면서 안전하게 참조 가능
    TWeakObjectPtr<AActor> TargetEnemy;

    void AttackEnemy();
};

// 구현부
AMyHero::AMyHero()
{
    // MakeShared를 사용하여 메모리 할당 최적화 (제어 블록과 객체를 한 번에 할당)
    // 이것이 'new FCombatCalculator' 보다 성능상 우위
    CombatCalc = MakeShared<FCombatCalculator>();
}

void AMyHero::BeginPlay()
{
    Super::BeginPlay();

    // 예시: 월드의 첫 번째 액터를 타겟으로 설정
    // 실제로는 Trace 등을 통해 얻어옴
    if(GetWorld())
    {
        // TargetEnemy = ...; 
    }
}

void AMyHero::AttackEnemy()
{
    // 1. WeakObjectPtr 유효성 체크 (TargetEnemy가 Destroy 되었는지 확인)
    if (TargetEnemy.IsValid())
    {
        // 2. SharedPtr 유효성 체크
        if (CombatCalc.IsValid())
        {
            float Dmg = CombatCalc->CalculateDamage(10.0f);
            UE_LOG(LogTemp, Warning, TEXT("Attacking %s with %f damage"), *TargetEnemy->GetName(), Dmg);
        }
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("Enemy is already dead or null."));
    }
}

4. 핵심 요약 및 최적화 팁

  1. MakeShared 사용: TSharedPtr<T>(new T()) 대신 MakeShared<T>()를 사용하십시오. 메모리 할당을 2회에서 1회로 줄여 캐시 효율과 성능을 높여준다.
  2. 스레드 안전성: 언리얼 스마트 포인터는 기본적으로 스레드 세이프하지 않다. 다른 스레드에서 접근해야 한다면 TSharedPtr<MyClass, ESPMode::ThreadSafe>와 같이 템플릿 인자를 추가해야 한다. (단, 약간의 오버헤드 발생)
  3. UPROPERTY와의 관계: UObject를 멤버 변수로 가질 때, 해당 객체가 계속 살아있어야 한다면(소유) 스마트 포인터 대신 UPROPERTY() 매크로를 붙여 GC가 수거하지 못하게 해야한다. 참조만 한다면 TWeakObjectPtr를 사용.

'프로그래밍 > Unreal Engine 5' 카테고리의 다른 글

[Unreal Engine 5] UPROPERTY()와 지정자(Specifier)들  (0) 2026.01.15
[Unreal Engine 5] 액터의 라이프 사이클  (2) 2026.01.09
[Unreal Engine 5] Transform Direction  (0) 2025.12.04
[Unreal Engine 5] 움직이는 발판 위 문제  (0) 2025.12.03
[Unreal Engine 5] 언리얼에서의 레벨 디자인  (0) 2025.12.02
'프로그래밍/Unreal Engine 5' 카테고리의 다른 글
  • [Unreal Engine 5] UPROPERTY()와 지정자(Specifier)들
  • [Unreal Engine 5] 액터의 라이프 사이클
  • [Unreal Engine 5] Transform Direction
  • [Unreal Engine 5] 움직이는 발판 위 문제
hanong8
hanong8
hanong8 님의 블로그 입니다.
  • hanong8
    HaNong
    hanong8
  • 전체
    오늘
    어제
    • 분류 전체보기 (103) N
      • 프로그래밍 (100) N
        • Unreal Engine 5 (45)
        • C++ (22)
        • UML (2)
        • 자료구조 (2)
        • 알고리즘 (9)
        • 개발일지 (4)
        • DirectX11 (5)
        • Git (2)
        • 코드카타 (9) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hanong8
[Unreal Engine 5] 언리얼 엔진 5의 스마트 포인터
상단으로

티스토리툴바