언리얼 C++ 프로그래밍

[언리얼 C++] 쉐어드 포인터 사용법

언린이 2021. 7. 21. 20:55

쉐어드 포인터는 기본적으로 메모리 누수를 방지하고 초기화되지 않은 메모리에 대한 포인터를 방지한다는 점에서 기본 스마트 포인터의 모든 이점을 포함하고 있으며 다음과 같은 추가 기능들도 제공합니다.

  • 공유 소유권 : 여러 쉐어드 포인터가 참조하는 데이터 개체는 참조 카운트가 0이 되지 않는 한 소멸되지 않습니다.
  • 자동 무효화 : 댕글링 포인터에 대한 걱정없이 안전하게 참조할 수 있습니다.
  • 약한 참조 : 위크 포인터는 참조 주기를 깰 수 있습니다.
  • 의도의 표시 : 소유자를 관찰자와 구별하고 무효화할 수 없는 참조를 제공합니다.

 

쉐어드 포인터에는 주목할 만한 몇가지 기본 특성이 존재합니다.

  • 매우 견고한 신택스
  • 비간섭
  • 스레드 안전
  • 좋은 성능, 메모리 관리

 

쉐어드 포인터에는 위와 같은 기능들과 특징이 존재하기 때문에 많이 사용되고 있습니다.

이제 쉐어드 포인터의 사용법에 대해 알아보도록 하겠습니다.

 

 

1. 선언 및 초기화

// Create an empty shared pointer
TSharedPtr<FMyObjectType> EmptyPointer;

// Create a shared pointer to a new object
TSharedPtr<FMyObjectType> NewPointer(new FMyObjectType());

// Create a Shared Pointer from a Shared Reference
TSharedRef<FMyObjectType> NewReference(new FMyObjectType());
TSharedPtr<FMyObjectType> PointerFromReference = NewReference;

// Create a Thread-safe Shared Pointer
TSharedPtr<FMyObjectType, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FMyObjectType, ESPMode::ThreadSafe>(MyArgs);

위 예제 코드처럼 쉐어드 포인터를 선언하고 초기화하는 여러가지 방법들이 있습니다.

 

// Increase the reference count of whatever object ExistingSharedPointer references.
TSharedPtr<FMyObjectType> AnotherPointer = ExistingSharedPointer;

그리고 쉐어드 포인터를 복사할때마다 시스템은 참조하는 개체에 대한 참조 카운트를 한개씩 증가시킵니다.

여기서 참조된 개체는 참조하는 쉐어드 포인터가 한개라도 존재한다면 소멸되지 않고 유지됩니다.

 

PointerOne.Reset();
PointerTwo = nullptr;
// Both PointerOne and PointerTwo now reference nullptr.

Reset() 함수를 사용하여 쉐어드 포인터를 재설정하거나 null 포인터를 할당할 수 있습니다.

 

// Move the contents of PointerOne over to PointerTwo. PointerOne will reference nullptr after this.
PointerTwo = MoveTemp(PointerOne);

// Move the contents of PointerTwo over to PointerOne. PointerTwo will reference nullptr after this.
PointerOne = MoveTempIfPossible(PointerTwo);

MoveTemp() 함수를 사용하여 하나의 쉐어드 포인터의 내용을 다른 쉐어드 포인터로 전송할 수 있습니다.

전송된 이후에 파라미터로 넘겨준 쉐어드 포인터는 null 포인터가 됩니다.

 

 

2. 쉐어드 포인터와 쉐어드 레퍼런스 간에 변환

쉐어드 포인터와 쉐어드 레퍼런스 간에 변환하는 것은 일반적인 방법입니다.

 

TSharedPtr<FMyObjectType> MySharedPointer = MySharedReference;

쉐어드 레퍼런스는 암시적으로 쉐어드 포인터로 변환하고 쉐어드 포인터가 유효한 개체를 참조할 것이라는 추가 보증을 제공합니다. 위 예제 코드처럼 쉐어드 레퍼런스 => 쉐어드 포인터 로의 변환은 할당 연산자를 통해 암시적으로 변환이 가능합니다.

 

// Ensure your shared pointer is valid before dereferencing to avoid a potential assertion.
if (MySharedPointer.IsValid())
{
    MySharedReference = MySharedPointer.ToSharedRef();
}

쉐어드 포인터가 null이 아닌 개체를 참조하는 한 ToSharedRef() 함수를 사용하여 쉐어드 레퍼런스를 만들 수 있습니다.

쉐어드 포인터가 null인 상태에서 쉐어드 레퍼런스를 만들려고 시도하면 어서트가 발생하게 됩니다.

 

 

3. 비교

TSharedPtr<FTreeNode> NodeA, NodeB;
if (NodeA == NodeB)
{
    // ...
}

쉐어드 포인터는 operator==을 사용하여 비교할 수 있습니다.

여기서 비교하는 부분은 두 쉐어드 포인터가 동일한 개체를 참조하는지에 대한 것입니다.

 

if (Node.IsValid())
{
    // ...
}

if (Node)
{
    // ...
}

if (Node.Get() != nullptr)
{
    // ...
}

IsValid() 함수 또는 쉐어드 포인터 자체를 사용하여 유효한 개체를 참조하는지를 판단할 수 있습니다.

그리고 Get() 함수를 사용하여 쉐어드 포인터가 유효한 개체 포인터를 반환하는지도 확인할 수 있습니다.

 

 

4. 참조 및 액세스 해제

// Check that Node references a valid object before dereferencing.
if (Node)
{
    // Any of the following three lines of code will dereference Node and call ListChildren on its object:
    Node->ListChildren();
    Node.Get()->ListChildren();
    (*Node).ListChildren();
}

 

위 예제 코드처럼 ListChildren() 함수를 사용하여 개체에 대한 참조 해제 및 액세스 해제를 할 수 있습니다.

참조 해제를 하기 전에 쉐어드 포인터의 타당성 검사를 하는 것을 잊지 마시기 바랍니다.

 

 

5. 사용자 지정 삭제기

void DestroyMyObjectType(FMyObjectType* ObjectAboutToBeDeleted)
{
    // Custom deletion code goes here.
}

// These functions create Smart Pointers with custom deleters.
TSharedRef<FMyObjectType> NewReference(new FMyObjectType(), [](FMyObjectType* Obj){ DestroyMyObjectType(Obj); });
TSharedPtr<FMyObjectType> NewPointer(new FMyObjectType(), [](FMyObjectType* Obj){ DestroyMyObjectType(Obj); });

쉐어드 포인터와 쉐어드 레퍼런스는 참조하는 개체에 대한 사용자 지정 삭제기를 지원합니다.

사용자 지정 삭제 코드를 실행하려면 위 예제 코드처럼 스마트 포인터를 생성할때 파라미터로 실행하려는 람다 함수를 넘겨주면 됩니다.