🚀 들어가며
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 참조를 더 명확하게 추적할 수 있는 방향으로 발전하고 있다는 점입니다.

위 구조처럼 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