메모리 관리, 스마트 포인터, 디버깅

2024. 6. 26. 19:29Unreal Engine

메모리 관리는 안전성이 높고 버그가 없는 프로그램을 작성하는 과정에서 늘 중요한 주제다. 허상 포인터(dangling pointer)는 메모리에서 지워진 대상을 참조하는 포인터이며 추적하기 어려운 버그를 만드는 대표적인 사례다.

 

UObject 참조 카운팅 시스템은 UObject 클래스로부터 파생된 액터와 클래스의 메모리를 관리하는 기본적인 수단으로

이를 통해 UE 프로그램 내에서 메모리가 관리된다.

 

UObject에서 파생되지 않은 커스텀 c++ 클래스를 작성한다면 TSharedPtr / TWeakPtr 참조 카운팅 클래스를 활용하면 된다.

이 클래스들을 활용하면 더 이상의 참조가 없을 때 자동으로 오브젝트를 삭제해준다.

 

 

Actor 클래스의 파생이 아닌 모든 UObject 파생 오브젝트를 생성할 때는 항상 NewObject<>를 사용해야 한다.

오브젝트가 Actor 또는 그 파생일 때는 SpawnActor<>를 사용해야 한다.

다음과 같이 UAction 클래스의 인스턴스를 생성하면 된다.

1
2
3
4
5
6
7
8
9
void AChapter_03GameModeBase::BeginPlay()
{
    // Create an object
    UAction * action = NewObject<UAction>(GetTransientPackage(), 
                                          UAction::StaticClass() 
                                          /* RF_* flags */ ); 
 
}
 
cs

UAction::StaticClass()를 사용하며 UAction 오브젝트의 베이스인 UClass* 를 얻을 수 있다

NewObject<>의 첫 인자는 GetTransientPackage()로 단순히 게임의 휘발성 패키지를 얻어온다

UClass 인스턴스를 선택하기 위해서 블루프린트에서 UPROPERTY() TSubclassOf<AActor>를 사용 할 수도 있다.

세번째 인자는 메모리 관리 시스템이 어떤 방식으로 UObject를 다룰지 지정하는 파라미터의 조합이다.

 

NewObject<>와 비슷한 함수로 ConstructObject<> 함수는 생성시 좀 더 많은 파라미터를 제공한다.

 

관리되는 메모리 - 메모리 해제

 UObject 인스턴스는 참조 카운트를 지원해 모든 참조가 사라지면 가비지 컬렉션 대상이 된다.

ConstructObject<> 또는 NewObject<> 를 사용한 UObject 클래스 파생 오브젝트도 참조 카운트가 0으로 떨어지기 전에

UObject::ConditionalBeginDestroy()멤버 함수를 호출해 수동으로 메모리에서 해제할 수도 있다.

 

 

1
2
3
4
5
6
7
/ Create an object
    UAction * action = NewObject<UAction>(GetTransientPackage(), 
                                          UAction::StaticClass() 
                                          /* RF_* flags */ ); 
 
    // Destroy an object
    action->ConditionalBeginDestroy();
cs

ConditionalBeginDestroy() 명령은 메모리 해제 절차를 시작하며 재정의 가능한 Destroy()와 FinishDestroy() 함수를 호출한다.

다른 오브젝트가 여전히 참조하고 있는 오브젝트에 대해 호출되지 않도록 조심해야만 한다.

 

관리되는 메모리 - 오브젝트 추적을 위해 스마트 포인터 사용

(TSharedPtr, TWeakPtr, TAutoPtr)

 

원시 포인터를 사용하지 않길 원하면서 c++ 코드에서 수동으로 오브젝트를 추적하고 삭제하는 상황에서 쓰기 좋다.

new 키워드를 사용해 동적으로 할당되는 오브젝트를 사용한다면 이를 참조 카운트를 지원하는 포인터로 감싸서 자동으로 메모리 해제가 일어나도록 할 수 있다.

TSharedPtr : 모든 커스텀 c++ 오브젝트를 참조 카운트 방식으로 만든다. 스레드로부터 안전한(ESPMode::ThreadSafe 를 전달 한 경우) 참조 카운트 포인터로 공유 오브젝트를 나타낸다 공유 오브젝트는 더 이상 참조가 없을 때 할당 해제된다.

TAutoPrt : 스레드로부터 안전하지 않은 공유 포인터

 

1
2
Class MyClass{};
TSharedPtr<Myclass> shredPtr(new My Class());
cs


 짧은 세그먼트를 사용해 앞서 언급했던 네 가지 타입의 스마트 포인터 사용을 시연할 수 있다.

 

약한 포인터(weak pointer)와 공유 포인터(shared point) 사이에는 약간의 차이가 있다

약한 포인터는 참조 카운터가 0으로 내려갈 때 지우지 않고 유지하는 기능이 없다.

약한 포인터를 사용하면 오브젝트를 수동으로 삭제할 때 약한 포인터의 참조가 NULL이 된다.

 

공유 포인터는 스레드로부터 안전하다 이는 기본 오브젝트를 별도의 스레드에서 안전하게 조작 할 수 있음을 의미한다. TSharedRef는 UObject 또는 그 파생에 사용 할 수 없으며 커스텀 c++ 클래스에서만 사용 할 수 있다.

TSharedPtr의 스레드 안전성을 보장하지 않아도 된다면 TAutoPtr을 사용할 수 있다. 이는 참조의 수가 0으로 떨어지면 자동으로 오브젝트를 지운다.

 

언리얼의 가비지 컬렉션 시스템과 UPROPERTY()

UCLASS() 멤버로 UPROPERTY() 같은 오브젝트가(TArray<> 등) 있을 때 UPROPERTY()로 선언해야 한다.

그래야 참조 카운트가 재대로 계산되고 그렇지 않다면 예상치 못한 타입의 메모리 관련 버그가 발생 할 수 있다.

UPROPERTY() 선언은 UE 에게 제대로 된 메모리 관리를 받아야 한다는 것을 알려준다.

 

 

'Unreal Engine' 카테고리의 다른 글

UE5 PossessedBy() 와 OnRep_ 함수  (0) 2024.07.25
UnrealEngine EnhancedInput  (0) 2024.07.25
Garbage Collection(가비지 컬렉션)  (0) 2024.06.28
커스텀 엑터 생성하기  (0) 2024.06.26
클래스 생성  (0) 2024.06.26