UE4는 C++11 스마트 포인터의 커스텀 구현을 제공하고 있으며 다음과 같은 이점을 제공합니다.
- 메모리 누수 방지
스마트 포인터(TWeakPtr, TWeakObjectPtr 제외)는 공유 참조가 존재하지 않을 때 객체를 자동으로 해제합니다. - 약한 참조
약한 포인터는 공유 참조에서 발생 할 수 있는, 순환 참조 문제와 삭제된 오브젝트를 참조하는(dangling) 포인터 문제를 방지할 수 있습니다. - 스레드 안전 옵션
언리얼 스마트 포인터 라이브러리에는 멀티스레드에 걸쳐 참조 카운팅을 관리하는 코드인 스레드 세이프(thread-safe) 코드가 포함되어 있습니다. 스레드 안정성이 필요하지 않다면 그 대신에 향상된 퍼포먼스를 구현할 수 있습니다. - 런타임 안전
TSharedRef는 절대 null 일 수 없으며 언제든지 참조 해제될 수 있습니다. - 명확한 의도
관찰자 중에서 오브젝트의 소유자를 쉽게 분별할 수 있습니다. - 메모리
스마트 포인터는 64 비트의 C++ 포인터 크기의 두 배입니다 (공유된 16 바이트의 레퍼런스 컨트롤러도 포함). 단, 예외로 유니크 포인터만 C++ 포인터의 크기와 같습니다.
TSharedPtr
- TSharedPtr은 UObject를 가리킬 수 없습니다.
- UObject를 가리키고 싶다면 TWeakObjectPtr을 사용할 수 있습니다.
- TSharedPtr은 네이티브 객체에 사용할 수 있습니다.
TSharedPtr은 객체의 참조 카운터입니다. 참조 카운트가 0일 때 소멸됩니다.
주의!!! 동일한 객체를 참조하는 두 TSharedPtr이 서로 순환 참조할 경우 절대로 메모리가 해제 될 수 없습니다.
언리얼 엔진 GC 시스템은 UObject를 위한 것이며 UObject는 이미 TSharedPtr로 메모리 관리됩니다.
만약에 UObject를 TSharedPtr로 만드려고 한다면,
// header
UCLASS()
class TESTPROJECT_API UMyObject : public UObject
{
GENERATED_BODY()
...
};
// cpp
UMyObject* MyObject = NewObject<UMyObject>();
TSharedPtr<UMyObject> SharePtr2 = MakeShareable(MyObject);
UObject 해제시 Assertion failed가 발생하며 어플리캐이션 크래시를 일으킨다.
Assertion failed: GetFName() == NAME_None [File:D:/Build/++UE4/Sync/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectBase.cpp] [Line: 130]
UObjectBase::~UObjectBase()
{
// If not initialized, skip out.
if( UObjectInitialized() && ClassPrivate && !GIsCriticalError )
{
// Validate it.
check(IsValidLowLevel());
check(GetFName() == NAME_None); // <= 여기에 걸림
GUObjectArray.FreeUObjectIndex(this);
}
}
TSharedRef
- 공유 참조는 null을 허용하지 않습니다.
- UObject 오브젝트에는 사용할 수 없습니다.
- IsValid() 함수 없음
TSharedPtr과 동일합니다. 단, TSharedRef는 항항 null이 될 수 없는 오브젝트를 참조해야 합니다. 이러한 이유로 TSharedRef는 언제든지 TSharedPtr이 될 수 있습니다. 참조된 오브젝트가 null이 불가능하다는 것을 보증하길 원할 경우 TSharedRef를 사용합니다.
TSharedRef <-> TSharedPtr 변환
// TSharedRef -> TSharedPtr
TSharedPtr<SimpleObject> objPtr = objRef;
// TSharedPtr -> TSharedRef
objRef3= objPtr.ToSharedRef();
TUniquePtr
하나의 포인터로 하나의 메모리를 다룬다는 개념입니다. 이는 여러 TUniquePtr이 하나의 메모리를 참조 할 수 없음을 의미한다. 하나의 포인터가 하나의 메모리를 소유하며 이 소유권을 공유하지 못하지만, 이전 할 수 있습니다.
생성
TUniquePtr<FString> myString = TUniquePtr<FString>();
// 선언
TUniquePtr<FMyBox> Box;
// 초기화
Box = MakeUnique<FMyBox>(10, 20);
이전
TUniquePtr<FString> youString = MoveTemp(myString);
주의!!! UObject가 아닌 것에서 동작합니다. UObject로 TUniquePtr를 초기화 할 경우 어플리캐이션 크래시를 발생시킵니다.
예를 들어 다음과 같은 코드를 작성하면 메모리가 해제될때 어플리케이션 크래시를 발생 시킵니다.
TUniquePtr<UMyObject> NewData = TUniquePtr<UMyObject>(NewObject<UMyObject>());
TWeakPtr
- 참조 카운팅에 참여하지 않습니다.
- 참조 대상이 소멸될 경우 자동으로 null이 됩니다.
- 참조 대상의 유효성을 검사 할 수 있음
TSharedPtr의 순환 참조 문제를 극복하기 위해 TWeakPtr을 사용합니다. 메모리를 참조하더라도 참조 카운트팅을 하지 않으며, 메모리의 라이프사이클에 영향을 주지 않습니다. TWeakPtr은 참조하는 대상이 소멸될 경우 자동으로 null 이 됩니다. 참조 대상의 유효성을 IsValid()를 사용하여 확인후 사용해 주어야 합니다.
생성
TSharedPtr<FMyData> MyData = MakeShareable(new FMyData());
TWeakPtr<FMyData> MyDataWeakPtr(MyData);
유효성 확인
if(MyDataWeakPtr.IsValid())
{
MyData->a = 10;
}
TSharedPtr로 변환
TSharedPtr<FMyData> MyDataSharedPtr(MyDataWeakPtr.Pin());
TWeakObjectPtr
TWeakPtr와 거의 비슷하며 차이가 있다면, TWeakPtr는 UObject를 다루지 못하지만 TWeakObjectPtr은 UObject를 다룹니다.
// OK
TWeakObjectPtr<UMyObject> WeakObjectPtr(NewObject<UMyObject>());
// 컴파일 에러
TWeakPtr<UMyObject> WeakPtr(NewObject<UMyObject>());
스마트 포인터 변환
StaticCastSharedPtr, ConstCastSharedPtr 캐스트 변환
TSharedPtr<SimpleObject> simpleObj;
TSharedPtr<ComplexObject> complexObj = MakeShared<ComplexObject>();
// 파생 클래스에서 기본 클래스로의 암시적 변환
simpleObj = complexObj;
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simpleObj is %s"), simpleObj.IsValid() ? TEXT("Valid") : TEXT("Not Valid"));
// 파생 클래스 StaticCastSharedPtr에 대한 기본 클래스
TSharedPtr<SimpleObject> simpleObj2 = StaticCastSharedPtr<SimpleObject>(complexObj);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"complexObj2 is %s"), simpleObj2.IsValid() ? TEXT("Valid") : TEXT("Not Valid"));
// 상수가 아닌 것을 상수로 변환 ConstCastSharedPtr
const TSharedPtr<SimpleObject> simpleObj_const(new SimpleObject());
TSharedPtr<SimpleObject> simpleObj_mutable = ConstCastSharedPtr<SimpleObject>(simpleObj_const);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"simpleObj_mutable is %s"), simpleObj_mutable.IsValid() ? TEXT("Valid") : TEXT("Not Valid"));
출력 결과,
LogTemp: Warning: ATestProjectGameModeBase::StartPlaysimpleObj is Valid
LogTemp: Warning: ATestProjectGameModeBase::StartPlaycomplexObj2 is Valid
LogTemp: Warning: ATestProjectGameModeBase::StartPlaysimpleObj_mutable is Valid
출처
https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/SmartPointerLibrary/
https://www.cnblogs.com/shiroe/p/14729821.html
'게임 개발 > Unreal Engine 기본' 카테고리의 다른 글
[UE4] 액터의 수명 주기, 가비지 콜렉터 (0) | 2022.02.10 |
---|---|
[UE4] 언리얼 오브젝트의 기능 (0) | 2022.02.10 |
[UE4] 문자열 변환 - FName, FText, String (2) | 2022.02.09 |
[UE4] 문자열 다루기 - FName, FText, String (0) | 2022.02.09 |
[UE4] 리플렉션 (0) | 2022.02.08 |