UE5

언리얼 내의 포인터

게임 개발 2023. 11. 2. 14:52

 

 

 

관리되지 않는 메모리와 관리되는 메모리

 

 

 관리되지 않는 메모리는 malloc()/free() , new/delete가 있으며,

 

관리되는 메모리는 NewObject< >와 ConstructObject< > 사용이 있다.

또한 이들을 수동 메모리 해제를 위해 언리얼은 UObject::ConditionalBeginDestroy() 멤버 함수를 호출한다.

오브젝트 추적에 대한 용이함으로는 스마트 포인터와 TScopedPointer를 사용하여 가능하다.

 

 

 

 

 

언리얼 스마트 포인터

 

 

언리얼 스마트 포인터란?

 

 C++11의 스마트 포인터의 디자인을 커스텀하여 구현(implementation. SW공학 내의 구현 의미)한 것으로,

C++의 스마트 포인터 라이브러리는 일반 객체를 위한 라이브러리이므로, 언리얼 오브젝트에 사용하기에는 무리가 있다.

언리얼 오브젝트는 언리얼 엔진 가상머신의 가비지 컬렉션에 의해 자동으로 관리되기 때문이다.

또한 이러한 구현 내에는 nullable이 아닌 공유 포인터처럼 작동하는 공유 참조가 있다.

종류로는 TSharedPtr(공유 포인터), TSharedRef(공유 참조), TWeakPtr(약한 포인터), TUniquePtr(고유 포인터) 가 있다.

 

!중요!

UObject Unreal 포인터 사용법

UObject Unreal 개체는 게임 코드에 더 잘 맞는 별도의 메모리 추적 시스템을 이용하기 때문에

이러한 클래스는 해당 시스템과 함께 사용할 수 없다 .

 

그럼 UObject는 어떻게 포인터를 사용할까?

원시 포인터의 사용법도 있지만, 

언리얼 5에서는 기존의 원시 포인터를 대체하기 위한 UObject용 포인터를 제공하고있다.

원시 포인터가 필요하고 묵시적 변환을 사용할 수 없는 경우에는

TObjectPtr과 ToPawPtr 또는 Get을 호출할 수 있다.

 

 

 

사용 예시

 

 

- 필자는 첫 스마트 포인터 시도 때,

UObject 객체에 스마트 포인터를 욱여넣으니까 코드 작성시에 오류는 뜨지 않으나

디버깅 시 실행이 되지 않아 당황스러워 문서를 다시 읽어나갔다.

 

 

언리얼 스마트 포인터의 이점

 

1. 메모리 누수 방지로 스마트 포인터는(weak ptr 제외)

더 이상의 참조가 필요하지 않으면 자동 메모리 해제가 되는 이점이있다. 

메모리 관리는 안정성이 높고 버그가 없는 프로그램을 작성하는 과정에서 늘 중요한 주제인 만큼

스마트 포인터의 메모리 누수 방지는 큰 이점이 된다.

 

2. c++ 약한 참조의 이점을 그대로 가져왔다.

이는 서로 공유되어 reference cycle을 가지는 순환구조를 막을 수 있다.

 

3. 선택적 스레드 안전성

언리얼 스마트 포인트 라이브러리는 thread-safe code가 포함되어있다.

 

4. 런타임시 자동 해제로 인해 안정성이 높다

공유 참조는 null이 아니며 항사 역참조가 가능하다.

이는 포인터를 잘못 사용하게되면 계속 있어야하는 메모리가 없어져

실행이 안되기도 하고, 순환사이클로 인해 삭제하기 어렵게된다

때문에 포인터는 늘 양날의 검이된다.

포인터를 이용할 땐 생명 주기를 잘 조절하자.

 

5. 정의로 인한 추적의 유용함을 가진다.

객체를 소유하는 변수와 옵저버를 쉽게 구분할 수 있다.

 

6. 메모리적으로 이점을 가진다.

언리얼 오브젝트는 가비지 컬렉션에 의해 메모리가 자동으로 관리된다.

하지만 가비지 컬렉션 시스템에 의해 관리되기 때문에

메모리에서 해지되는 타이밍을 정확히 예측할 수 없기 때문에

포인터를 통해 사용자가 메모리를 관리할 수 있다.

 

 

<응용 예시>

해당 캡처본은 UObject 개체라 이렇게 쓰면 안된다.

 

 

 

스마트 포인터 유형

 

스마트 포인터는 포함하거나 참조하는 개체의 수명에 영향을 미칠 수 있다.

스마트 포인터마다 개체에 대한 제한 사항과 효과가 다르다.

다음 표는 각 유형의 스마트 포인터를 사용하는 것이 적절한 시기를 결정하는 데 도움이 될 수 있다.

 

 

스마트 포인터 유형 사용 사례
공유 포인터
(TSharedPtr)
공유 포인터는 자신이 참조하는 개체를 소유하여 해당 개체의 삭제를 무기한 방지하고
공유 포인터나 공유된 참조가 이를 참조하지 않을 때 궁극적으로 삭제를 처리한다.
공유 포인터는 비어 있을 수 있다. 즉, 어떤 개체도 참조하지 않는다.
null이 아닌 공유 포인터는 참조하는 개체에 대한 공유하는 참조를 생성할 수 있다.
공유 참조
(TSharedRef)
공유 참조는 참조하는 객체를 소유한다는 점에서 공유 포인터처럼 작동한다.
null 개체와 관련하여 다르다.
공유 포인터에는 이러한 제한이 없기 때문에 공유 참조는 항상 공유 포인터로 변환될 수 있으며
해당 공유 포인터는 유효한 객체를 참조하도록 보장된다.
참조된 객체가 null이 아니라는 것을 보장하고 싶거나
공유 객체 소유권을 표시하려는 경우 공유 참조를 사용하는 것이 좋다.
약한 포인터
(TWeakPtr)
약한 포인터는 공유 포인터와 유사하지만 참조하는 개체를 소유하지 않으므로
수명 주기에 영향을 주지 않는다.
이 속성은 참조 순환을 깨기 때문에 매우 유용할 수 있지만,
약한 포인터가 언제든지 경고 없이 null이 될 수 있음을 의미하기도 한다.
이러한 이유로 약한 포인터는 참조하는 개체에 대한 공유 포인터를 생성하여
프로그래머가 일시적으로 개체에 안전하게 액세스할 수 있도록 보장한다.
고유 포인터
(TUniquePtr)
고유 포인터는 자신이 참조하는 개체를 단독으로 명시적으로 소유한다.
특정 리소스에 대한 고유 포인터는 하나만 있을 수 있으므로
고유 포인터는 소유권을 이전할 수 있지만 공유할 수는 없다.
고유 포인터를 복사하려고 하면 컴피일 오류가 발생한다.
고유 포인터가 범위를 벗어나면 참조하는 개체를 자동으로 삭제한다.

 

*주의*
고유 포인터가 참조하는 객체에 대한 공유 참조를 만드는 것은 위험하다.
다른 스마트 포인터가 여전히 개체를 참조하더라도
자체 소멸 시 개체를 삭제하는 고유 포인터의 동작이 일시 중지되지 않는다.
마찬가지로 공유 포인터나 공유 참조에서 참조하는 개체에 대한 고유 포인터를 만들어서는 안된다.

 

 

Helper Classes and Functions

 

언리얼 스마트 포인터 라이브러리는 스마트 포인터를 더욱 쉽고 직관적으로 사용할 수 있도록

여러 핼퍼 클래스와 함수를 제공한다.

 

 

Helper Description
Classes  
TSharedFromThis TShredFromThis로 클래스를 파생하면 AsShared 또는 ShredThis 함수가 추가된다.
이 함수를 사용하면 객체가 공유 참조 (TSharedRef) 를 하게 된다.
Functions  
MakeShred and
MakeShreable
Shared Pointer는 일반적인 C++ 포인터로 만든다.
MakeShred 는 새로운 개체의
인스턴스와 레퍼런스 컨트롤러를 단일 메모리 블록에 할당하지만
개체의 public에 생성자를 제공받아야 한다.
MakeShreable은 효율성이 떨어지지만 객체의 생성자가 private인 경우에도 작동하며,
생성자를 허락하지 않은 객체의 소유권을 가질 수 있다.
객체를 삭제할 때 사용자 비해비이어트리를 지원하기 때문이다.
StaticCastShredRef and
StaticCastShredPtr
static cast utility 함수로 일반적으로 부모 클래스의 downcast를 하는데 사용된다.
ConstCastSharedRef and
ConstCastSharedPtr
const smart Ref 또는 smart Ptr을 a mutable(not-const) smart Ref 또는 smart Ptr로 변환시킨다.

 

 

 

스마트 포인터 구현의 세부 정보

 

언리얼 스마트 포인터 라이브러리의 스마트 포인터들은

모두 기능과 효율 측면에서 몇 가지 일반적인 특징들을 공유한다.

 

속도

 

스마트 포인터 사용을 고려할 때는 항상 성능을 염두에 두어야한다.

스마트 포인터는 특정 하이 레벨 시스템이나 자원 관리 또는 툴 프로그램에 매우 적합하지만,

일부 스마트 포인터 타입은 C++ 기본 포인터보다 더 느리며,

이런 오버헤드로 인해 렌더링과 같은 로우 레벨 엔진 코드에는 유용하지 않다.

 

스마트 포인터의 일반적인 성능의 이점을 가질 경우 :

 

- 모든 연산이 고정비(constant-time)일 때

- 빌드를 할 때, 대부분의 스마트 포인터들을 참조, 해제하는 속도가 C++ 원시 포인터 만큼 빠르다.

- 스마트 포인터는 복사해도 메모리를 할당하지 않는다.

- 스레드 안전성으로 스마트 포인터는 잠금 없는 구조이다.

 

스마트 포인터의 성능적 문제점을 가질 경우 :

 

- 스마트 포인터의 생성 및 복사는 C++ 원시 포인터의 생성 및 복사보다 더 많은 오버헤드가 발생될 때

- 참조되는 카운트가 유지되면서 사이클을 가질 경우

- 일부 스마트 포인터는 C++의 원시 포인터보다 더 많은 메모리를 사용할 때가 있다.

- 참조 컨트롤러에는 두 가지 힙 할당이 있는데,
MakeShreable 을 사용하는 대신 MakeShared를 사용하면 
두 번 할당되는 것을 피할 수 있으며, 성능을 향상시킬 수 있다.

 

 

 

 

 

해당 게시글은 C++를 이용한 언리얼 엔진4 개발 2판과

https://docs.unrealengine.com/5.1/en-US/smart-pointers-in-unreal-engine/

 

Unreal Smart Pointer Library

Custom implementation of shared pointers, including weak pointers and non-nullable shared references.

docs.unrealengine.com

를 참고하여 제작되었습니다.

'UE5' 카테고리의 다른 글

GitHub에 프로젝트 저장하기  (0) 2023.12.29
Delegate 사용방법  (1) 2023.12.05
언리얼 게임 모드 클래스  (0) 2023.10.27
UE5 멀티스레딩과 enum을 이용한 상태 변환  (1) 2023.10.22
UFUNCTION() 응용을 위한 가이드  (1) 2023.10.16