나만의 작은 도서관
[TIL][C++] 251010 MMO 서버 개발 112일차: [언리얼] TSharedRef는 항상 유효한 객체를 가리킴을 보장한다, IHttpRequest 객체를 지역변수로 선언해도 괜찮은 이유 본문
Today I Learn
[TIL][C++] 251010 MMO 서버 개발 112일차: [언리얼] TSharedRef는 항상 유효한 객체를 가리킴을 보장한다, IHttpRequest 객체를 지역변수로 선언해도 괜찮은 이유
pledge24 2025. 10. 10. 22:34주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
[언리얼] TSharedRef는 항상 유효한 객체를 가리킴을 보장한다.
- 쉐어드 레퍼런스라고 불리는 TSharedRef는 초기화하거나 null을 할당할 수 없는 스마트 포인터이다. 즉, 엔진의 UObject 시스템 외부에 있는 데이터 오브젝트에 대한 강력한 null 비허용 스마트 포인터로, “null을 허용하지 않는다” 특성이 TSharedPtr과 큰 차이점이다.
- 이는 TSharedRef가 재설정되거나, null 오브젝트를 할당하거나, 빈 오브젝트를 생성할 수 없음을 의미하기도 한다.
- 따라서, TSharedRef는 “항상 유효한 오브젝트”가 포함되며, 이러한 이유로 null체크를 하는 IsValid() 메서드도 없다.
선언 및 초기화 예시
- TSharedRef는 항상 유요한 오브젝트가 포함되어 있어야 하므로, 선언과 동시에 초기화가 강제된다.
- 선언과 초기화를 분리하여 코드를 작성할 경우, 컴파일 오류가 발생한다.
// 올바른 사용 방법(선언과 초기화를 동시에)
TSharedRef<FMyObjectType> ObjectRef = MakeShared<FMyObjectType>();
// 잘못된 사용 방법(컴파일 오류)
TSharedRef<FMyObjectType> ObjectRef; // 초기화 X
TSharedRef<FMyObjectType> ObjectRef = nullptr; // nullptr로 초기화
// NullObject의 경우, 컴파일은 됨. But, NullObject가 실제로 null인지 확인.
TSharedRef<FMyObjectType> ObjectRef = NullObject;
TSharedRef와 TSharedPtr 사이의 변환은?
- TSharedRef와 TSharedPtr 사이의 변환(Casting)은 흔히 일어나는 작업이다. 변환은 그리 어렵지 않게 진행되는데, 그 이유는 TSharedRef가 암시적으로 TSharedPtr로 변환되며, 변환된 TSharedPtr이 가리키는 객체가 유효한 객체임을 보장하기 때문. 따라서 아래와 같이 TSharedRef → TSharedPtr 변환 시 TSharedPtr은 항상 유효한 포인터이다.
// MySharedPointer는 반드시 유효한 포인터!
TSharedPtr<FMyObjectType> MySharedPointer = MySharedReference;
- 반대로 TSharedPtr → TSharedRef로 변환해야하는 경우, TSharedPtr이 null이 아닌 오브젝트임을 보장하는 과정이 추가로 필요하다. 따라서 아래와 같이 IsValid 메서드로 한 번 확인해야 한다. (만약 null 오브젝트인 상태로 ToSharedRef()를 호출했다면, 프로그램이 assert를 발생시킨다.)
// TSharedPtr이 가리키는 객체가 유효한지 IsValid로 확인!
If (MySharedPointer.IsValid())
{
MySharedReference = MySharedPointer.ToSharedRef();
}
[언리얼] IHttpRequest 객체를 지역변수로 선언해도 괜찮은 이유
- 언리얼에서 HTTP 통신을 해야할 때 사용하는 IHttpRequest는 TSharedRef <IHttpRequest>와 같은 형태로 생성되고 관리된다.
- 이러한 ‘IHttpRequest를 함수의 지역변수로 선언하여 사용해도 생애 주기에 대한 문제가 발생하지 않을까?’하는 의문이 생겼다. 왜냐하면 response를 받았을 때 실행하는 로직은 다른 함수에서 진행되기 때문. response와 바인딩된 함수가 실행되는 시점에는 IHttpRequest 객체는 파괴되어 있을 것이다.
- 하지만 실제로는 그렇지 않았다. IHttpRequest 객체를 생성하는 방법이 아래와 같은데,
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
- 여기서 FHttpModule::Get().CreateRequest()의 반환타입이 TSharedRef <IHttpRequest>이며, 언리얼 엔진의 HTTP 시스템은 IHttpRequest 객체에 대해 작업이 완료되기 전까진 내부에서 refCount를 하나 올려 유지하기 때문
- → 따라서 지역변수로 IHttpRequest는 선언해도 ProcessRequest를 한 이후에도 FHttpModule이 Request 생명주기를 관리하기 때문에 함수 종료 이후에도 해당 객체는 유효하다.
void UMyClass::SendRequest()
{
TSharedRef<ihttprequest, espmode::threadsafe=""> Request = FHttpModule::Get().CreateRequest();
Request->SetURL("<https://example.com>");
Request->SetVerb("GET");
Request->OnProcessRequestComplete().BindUObject(this, &UMyClass::OnResponse);
Request->ProcessRequest(); // 이후에도 Request는 유효
}
</ihttprequest,>