언리얼의 TObjectPtr는 왜 등장했을까?

//
//

🚀 들어가며

Unreal Engine 4 시절의 C++ 코드를 보면 UObject 참조 변수를 아래와 같이 작성했습니다.

UPROPERTY()
AActor* TargetActor;

굉장히 익숙한 형태입니다.

UObject 계열 객체는 언리얼 GC가 관리하고,UPROPERTY()매크로를 붙이면 GC가 해당 참조를 추적할 수 있습니다.

그런데 Unreal Engine 5로 넘어오면서 새로운 형태가 등장했습니다.

UPROPERTY()
TObjectPtr<AActor> TargetActor;

처음 보면 이런 생각이 들 수 있습니다.

  • 원시 포인터(raw pointer)로 잘 쓰고 있었는데 왜 TObjectPtr가 등장했을까?

이런 오해도 생길 수 있습니다.

  • TObjectPtr는 std::shared_ptr 같은 스마트 포인터인가?

결론부터 말하면 TObjectPtr는 std::shared_ptr 같은 소유권 기반 스마트 포인터가 아닙니다.

TObjectPtr는 Unreal Engine의 UObject 참조를 더 명확하게 표현하고,
엔진이 객체 참조를 더 잘 추적할 수 있도록 만들기 위해 등장한 구조입니다.

이번 글에서는 UE4 시절 raw pointer 방식부터,
UE5에서 TObjectPtr가 등장한 배경까지 차근차근 정리해보겠습니다.


🧠 UE4 시절의 UObject 참조 방식

Unreal Engine 4에서는 UObject 참조를 보통 raw pointer로 작성했습니다.

UPROPERTY()
UObject* TargetObject;

Actor를 참조한다면 다음과 같이 작성했습니다.

UPROPERTY()
AActor* TargetActor;

이 방식이 가능했던 이유는 단순합니다.

언리얼은 C++ raw pointer 자체만 보고 객체를 추적하는 것이 아니라,
UPROPERTY()를 통해 Reflection 시스템에 등록된 정보를 기반으로 UObject 참조를 추적합니다.

즉, 핵심은 이것입니다.

  • raw pointer라서 안전한 것이 아니라 UPROPERTY로 Reflection 시스템에 등록되었기 때문에 GC가 추적할 수 있다.

 

따라서 UE4에서는 다음 구조가 기본이었습니다.

UPROPERTY()
AActor* TargetActor;

이 구조는 오랫동안 잘 동작했습니다.

그러면 왜 UE5에서는 TObjectPtr가 등장했을까요?


//

🎯 UE4 방식이 완전히 잘못된 것은 아닙니다

먼저 오해하지 말아야 할 점이 있습니다.

UE4에서 사용하던 raw UObject pointer 방식이 완전히 잘못된 방식은 아닙니다.

UPROPERTY()
AActor* TargetActor;

이 코드는 여전히 의미가 명확합니다.

  • TargetActor는 UObject 계열 객체를 가리킨다
  • UPROPERTY를 통해 Reflection 시스템에 등록된다
  • GC가 해당 참조를 추적할 수 있다

즉, 기존 방식도 언리얼 객체 시스템 안에서는 충분히 의미가 있었습니다.

하지만 UE5로 넘어오면서 엔진이 처리해야 하는 규모와 요구사항이 훨씬 커졌습니다.

여기서 TObjectPtr의 필요성이 등장합니다.


🚀 UE5 시대의 변화

UE4 시절의 UObject raw pointer 구조와 UE5에서 TObjectPtr가 등장하게 된 흐름을 그림으로 정리해보면 다음과 같습니다.

핵심은 UE5는 단순 raw pointer 사용을 넘어서, 엔진이 UObject 참조를 더 명확하게 추적할 수 있는 방향으로 발전하고 있다는 점입니다.

UE4의 raw pointer 방식과 UE5의 TObjectPtr 기반 UObject 참조 추적 구조 차이를 설명하는 Unreal Engine 인포그래픽
UE4의 raw pointer + UPROPERTY 구조에서 UE5의 TObjectPtr 기반 UObject 참조 추적 시스템으로 변화한 흐름을 정리한 이미지입니다.

위 구조처럼 UE5의 TObjectPtr는 단순 문법 변경이 아니라,
대규모 월드와 스트리밍 환경에 대응하기 위한 엔진 내부 객체 참조 시스템 변화의 일부라고 볼 수 있습니다.

특히

  • Object Handle 기반 확장 가능성
  • 에디터 검증 강화
  • GC 및 Reflection 추적 개선
  • UObject 참조 표현 명확화

같은 방향과 연결되면서 UE5의 객체 관리 구조가 점점 더 정교해지고 있습니다.

UE5는 단순히 렌더링 기능만 강화된 버전이 아닙니다.

엔진 전체적으로 더 큰 월드와 더 복잡한 객체 관리 구조를 다루는 방향으로 변화했습니다.

대표적으로 다음과 같은 흐름이 있습니다.

  • World Partition
  • 대규모 월드 스트리밍
  • 비동기 로딩
  • 에디터 검증 강화
  • 객체 참조 추적 강화
  • 에셋 관리 구조 확장

이런 환경에서는 엔진이 UObject 참조를 더 정교하게 이해할 필요가 생깁니다.

단순히

AActor* TargetActor;

라는 raw pointer만 있는 것보다,

TObjectPtr<AActor> TargetActor;

처럼 UObject 참조라는 사실이 타입 차원에서 명확하게 드러나는 구조가 더 유리합니다.


🧩 TObjectPtr는 무엇인가?

TObjectPtr는 UObject 계열 객체를 가리키기 위한 언리얼의 포인터 래퍼(Wrapper)입니다.

기본 사용 형태는 다음과 같습니다.

UPROPERTY()
TObjectPtr<AActor> TargetActor;

raw pointer와 비슷하게 사용할 수 있습니다.

if (TargetActor)
{
    TargetActor->Destroy();
}

하지만 의미는 조금 다릅니다.

AActor* 
→ C++ raw pointer

TObjectPtr<AActor>
→ Unreal Engine이 인식하기 쉬운 UObject 참조 래퍼

즉, TObjectPtr는 단순 문법 변경이 아니라, 언리얼 엔진이 UObject 참조를 더 명확하게 다루기 위한 구조라고 할 수 있습니다.


⚠️ TObjectPtr는 shared_ptr가 아닙니다

가장 중요한 오해를 먼저 정리해야 합니다.

TObjectPtr는 std::shared_ptr 같은 스마트 포인터가 아닙니다.

예를 들어, C++의 shared_ptr는 참조 카운트를 이용해 객체 생명주기를 관리합니다.

std::shared_ptr<Player> player = std::make_shared<Player>();

이 구조에서는 shared_ptr가 객체 소유권에 참여합니다.

하지만 TObjectPtr는 그런 방식이 아닙니다.

  • TObjectPtr는 UObject를 소유하지 않습니다.
  • TObjectPtr가 참조 카운트를 증가시키는 것도 아닙니다.
  • TObjectPtr가 delete를 호출하는 것도 아닙니다.

UObject의 생명주기는 여전히 언리얼의 UObject 시스템과 GC가 관리합니다.

즉, TObjectPtr는 👉 소유권 관리 도구가 아니라 UObject 참조 표현 방식에 가깝습니다.


🎮 그러면 왜 필요한가?

핵심은 이것입니다.

UE5는 UObject 참조를 raw pointer보다 더 명확하게 표현하고 싶어졌습니다.

raw pointer는 C++ 입장에서는 단순 주소입니다.

AActor* TargetActor;

이 코드만 보면 C++ 차원에서는 단순히 AActor 주소를 담는 포인터입니다.

하지만 언리얼 엔진 입장에서는 이 포인터가 더 많은 의미를 가집니다.

  • GC가 추적해야 하는 참조인가?
  • 에디터에서 검증할 수 있는 참조인가?
  • 로드되지 않은 객체를 가리킬 가능성이 있는가?
  • Object Handle 시스템과 연결될 수 있는가?
  • 향후 엔진 내부 추적 시스템에 참여할 수 있는가?

이런 요구사항을 생각하면, 단순 raw pointer보다 TObjectPtr 같은 래퍼 타입이 더 유리합니다.


🧠 UE4 방식과 UE5 방식 비교

UE4 스타일은 다음과 같습니다.

UPROPERTY()
AActor* TargetActor;

UE5 스타일은 다음과 같습니다.

UPROPERTY()
TObjectPtr<AActor> TargetActor;

겉으로 보기에는 큰 차이가 없어 보입니다.

하지만 엔진 구조 관점에서는 차이가 있습니다.

UE4 스타일
→ UObject raw pointer를 UPROPERTY로 등록

UE5 스타일
→ UObject 참조를 TObjectPtr 타입으로 명확히 표현

즉, UE5의 TObjectPtr는 UObject 참조를 엔진이 더 잘 추적할 수 있는 의도적인 형태라고 이해하면 좋습니다.


🔥 Object Handle과의 연결

TObjectPtr를 설명할 때 자주 등장하는 개념이 Object Handle입니다.

여기서 너무 깊게 들어가면 복잡해지지만, 개념적으로는 이렇게 이해할 수 있습니다.

raw pointer
→ 실제 객체 주소를 직접 가리킴

Object Handle
→ 객체를 직접 주소가 아니라 엔진이 관리하는 핸들 형태로 다룰 수 있음

Object Handle 기반 구조에서는, 엔진이 객체 참조를 더 유연하게 다룰 수 있습니다.

예를 들어

  • 객체가 아직 로드되지 않았는지
  • 객체 참조가 유효한지
  • 에디터에서 접근을 추적할 수 있는지
  • 향후 로딩/스트리밍 시스템과 연결할 수 있는지

같은 방향으로 확장할 수 있습니다.

TObjectPtr는 이런 방향의 기반이 될 수 있는 구조입니다.

즉, TObjectPtr는 단순히 포인터를 예쁘게 감싼 문법이 아니라,
UE5 이후의 객체 참조 추적 시스템을 위한 준비라고 볼 수 있습니다.


🧩 왜 에디터 빌드에서 특히 의미가 있을까?

TObjectPtr는 특히 에디터 환경에서 의미가 큽니다.

에디터가 없는 게임 실행 빌드에서 TObjectPtr는 raw pointer와 비슷한 역할을 한다고 공식 문서에서도 설명하고 있습니다.

하지만, 에디터에서는 엔진이 더 많은 검증과 추적을 수행할 수 있습니다.

예를 들어

  • 잘못된 UObject 참조를 감지하거나
  • 객체 접근을 추적하거나
  • 로드/언로드 관련 문제를 찾거나
  • 에디터 안정성을 높이는 데 활용할 수 있습니다

TObjectPtr는 런타임 성능을 위해서만 등장한 것이 아닙니다.

오히려 더 중요한 목적은 엔진과 에디터가 UObject 참조를 더 안전하게 추적할 수 있도록 만드는 것입니다.


🎯 UPROPERTY는 여전히 필요한가?

여기서 또 하나의 중요한 질문이 있습니다.

  • TObjectPtr를 쓰면 UPROPERTY는 없어도 될까?

일반적으로 GC 추적 대상 멤버라면 여전히 UPROPERTY가 중요합니다.

UPROPERTY()
TObjectPtr<AActor> TargetActor;

TObjectPtr는 UObject 참조를 표현하는 타입이고, UPROPERTY는 해당 멤버를 언리얼 Reflection 시스템에 등록하는 역할을 합니다.

즉, 둘의 역할은 다릅니다.

TObjectPtr
→ UObject 참조를 감싸는 타입

UPROPERTY
→ Reflection / GC / 직렬화 시스템에 등록하는 표시

따라서 단순히 TObjectPtr를 쓴다고 해서 모든 GC 추적 문제가 자동으로 해결된다고 이해하면 안 됩니다.


🧠 TObjectPtr를 단순화해서 보면

실제 구현은 엔진 내부에서 훨씬 복잡하지만, 개념적으로는 다음과 같이 생각해볼 수 있습니다.

template<typename T>
class TObjectPtr
{
public:
    T* Get() const;

    T* operator->() const;
    operator T*() const;

private:
    // 단순 raw pointer일 수도 있고,
    // 빌드 설정이나 엔진 내부 구조에 따라
    // Object Handle 형태로 관리될 수도 있음
    ObjectPointerType Object;
};

핵심은 이것입니다.

  • 사용자는 포인터처럼 사용하지만 엔진은 이 참조를 더 명확한 UObject 참조로 다룰 수 있다

이 구조가 TObjectPtr의 핵심입니다.


⚠️ 함수 인자나 지역 변수도 TObjectPtr로 바꿔야 할까?

일반적으로 TObjectPtr는 주로 UObject 멤버 변수, 특히 UPROPERTY로 관리되는 멤버에서 의미가 큽니다.

예를 들면, 아래 코드와 같은 상황에서 의미가 크다고 할 수 있습니다.

UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComponent;

반면, 함수 인자나 지역 변수까지 TObjectPtr로 바꿀 필요는 없습니다.

void SetTarget(AActor* NewTarget)
{
    TargetActor = NewTarget;
}

이런 형태는 여전히 자연스럽습니다.

따라서

멤버로 보관하는 UObject 참조
→ TObjectPtr 고려

잠깐 사용하는 지역 변수나 함수 인자
→ raw pointer 사용 가능

정도로 이해하면 좋습니다.


🎮 게임 엔진 관점에서 보면

게임 엔진은 단순 애플리케이션과 다릅니다.

게임 엔진에서는 객체가

  • 생성되고
  • 참조되고
  • 직렬화되고
  • 에디터에 노출되고
  • 스트리밍되고
  • GC로 관리됩니다

이 모든 과정에서 객체 참조를 안정적으로 추적하는 것이 중요합니다.

UE4에서는 raw pointer + UPROPERTY 조합으로 충분히 많은 문제를 해결했습니다.

하지만, UE5에서는 더 큰 규모의 월드와 더 복잡한 에디터/런타임 구조를 다루면서, UObject 참조를 더 명확하게 표현할 필요가 생겼습니다.

그래서 TObjectPtr가 등장한 것입니다.


🔥 중요한 포인트 정리

TObjectPtr를 이해할 때 가장 중요한 포인트는 다음입니다.

  • TObjectPtr는 std::shared_ptr 같은 소유권 기반 스마트 포인터가 아닙니다.
  • TObjectPtr는 UObject 계열 객체를 위한 포인터 래퍼입니다.
  • UE4에서는 raw pointer + UPROPERTY 방식이 일반적이었습니다.
  • UE5에서는 UObject 참조를 더 명확하게 표현하기 위해 TObjectPtr 사용이 권장됩니다.
  • UPROPERTY는 여전히 Reflection / GC 등록 관점에서 중요합니다.
  • TObjectPtr는 Object Handle, 에디터 검증, 참조 추적 같은 엔진 내부 확장 방향과 연결됩니다.

🧩 마무리

처음 TObjectPtr를 보면 단순히 이런 생각이 들 수 있습니다.

  • raw pointer를 왜 굳이 감싸지?
  • 스마트 포인터인가?
  • 성능 때문에 바뀐 건가?

하지만 실제로는 조금 다릅니다.

TObjectPtr는 단순히 C++ 문법을 현대적으로 바꾸려는 시도가 아닙니다.

👉 UE5 이후의 객체 참조 추적 시스템을 강화하기 위한 변화에 가깝습니다.

즉 핵심은 이것입니다.

TObjectPtr는 UObject를 소유하기 위한 도구가 아니라,
언리얼 엔진이 UObject 참조를 더 명확하게 추적하기 위한 도구입니다.

이 구조를 이해하면, 왜 UE5 코드에서 raw pointer 대신 TObjectPtr가 자주 등장하는지 자연스럽게 이해할 수 있습니다.


🔍 SEO 제목

언리얼의 TObjectPtr는 왜 등장했을까?

📝 메타 설명

Unreal Engine 5에서 TObjectPtr가 등장한 이유를 정리합니다. UE4의 raw pointer + UPROPERTY 방식부터 UE5의 UObject 참조 추적, Object Handle, GC, Reflection 시스템과의 관계까지 설명합니다.

🏷️ 태그

Unreal Engine
TObjectPtr
UObject
UPROPERTY
Reflection
Garbage Collection
Object Handle
UE5
UE4
C++
Game Engine
언리얼 엔진
언리얼 C++
언리얼 GC
UObject Pointer
//
   

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다