👩🏻‍💻기초지식/C++

[C++] 스마트 포인터 3가지, 언리얼의 스마트 포인터(TSharedRef), TSharedFromThis (AsShared, SharedThis)

공대 컴린이 2023. 5. 9. 19:35
728x90

스마트 포인터

스마트 포인터는 메모리 누수를 방지하고, 동적 메모리 할당과 해제에 대한 직접적인 관리를 피할 수 있는 포인터입니다.

또한 스마트 포인터는 C++11 표준 라이브러리에서 제공되며, 포인터 변수처럼 동작하지만 메모리를 자동으로 해제하도록 디자인되어있습니다.

 

대표적으로는 쉐어드 포인터(Shared Pointer), 위크 포인터 (Weak Pointer), 유니크 포인터(Unique Pointer) 세 가지가 있습니다.

std::shared_ptr

shared_ptr은 여러 개의 스마트 포인터가 동일한 객체에 대한 소유권을 공유할 수 있게 합니다.

내부적으로 참조 카운팅 방식을 사용하여 몇 개의 shared_ptr이 해당 객체를 참조하고 있는지 추적하며, 객체를 참조하는 포인터의 개수가 0이 될 때, 즉 마지막 shared_ptr이 사라질 때 객체가 자동으로 삭제됩니다.

 

std::weak_ptr

weak_ptr은 shared_ptr과 함께 사용되며, 순환 참조 문제를 해결하기 위해 설계되었습니다.

 

순환참조란, 두 객체가 서로를 참조함으로써 발생하는 문제로, 이 경우 두 객체 모두 절대로 파괴되지 않게되어 메모리 릭(memory leak, 누수)이 발생합니다.

 

또한, weak_ptr은 shared_ptr과 달리 소유권을 갖지 않으므로 참조 카운팅에 영향을 주지 않습니다. 이는 곧, 참조 주기에도 영향을 주지 않기 때문에 매우 유용하게 사용될 수 있지만, 다르게 말해 언제든지 사전 경고없이 null이 될 수 있습니다. weak_ptr이 참조하고 있던 shared_ptr의 참조가 끊어지면, weak_ptr은 null 값을 가지게 됩니다.

 

std::unique_ptr

unique_ptr은 한 번에 하나의 소유권만을 가질 수 있는 스마트 포인터입니다.

즉, 한 번에 하나의 unique_ptr만이 특정 객체에 대한 소유권을 가질 수 있습니다. 이러한 특성 때문에 unique_ptr은 복사될 수 없지만, std::move() 함수를 사용하여 소유권을 이전하는 것은 가능합니다.


언리얼의 스마트 포인터

언리얼 엔진4는 자체적으로 스마트포인터를 구현하고 있습니다. 자주 사용되는 스마트 포인터 타입은 TSharedPtr, TWeakPtr, TUniquePtr까지 C++의 스마트 포인터와 동일하고, 언리얼에서만 추가된 TSharedRef 가 존재합니다.

 

TSharedRef

C++의 shared_ptr과 weak_ptr, unique_ptr은 언리얼의 스마트 포인터와 기능적으로 유사하게 동작합니다.

언리얼에서 생긴 TSharedRef는, 항상 유효한(non-null) 참조를 보장하는 스마트 포인터로, 포인터 값에 null 참조가 들어올 수 없습니다. 따라서, 초기화 후에는 null을 할당할 수 없으며, 복사나 다른 인스턴스로 옮길 때만 변경될 수 있습니다.

 

쉐어드 레퍼런스는 언제나 쉐어드 포인터가 될 수 있고, 쉐어드 레퍼런스가 쉐어드 포인터로 변환된다면, 변환된 쉐어드 포인터는 유효한 오브젝트를 참조한다는 점이 보장됩니다. 따라서 쉐어드 레퍼런스는 참조한 오브젝트가 null이 아니라는것을 보장하거나, 공유된 오브젝트의 소유권을 보여주기 위한 경우에 사용이 적합합니다.

 

C++ STL의 스마트 포인터와 언리얼 스마트 포인터의 차이

1. 언리얼 엔진의 스마트 포인터는 언리얼 엔진의 메모리 관리 시스템인, 가비지 컬렉터를 사용하여 관리합니다. 반면, C++ STL의 스마트 포인터는 표준 C++ 메모리 할당 및 해제(new-delete)를 사용합니다.

 

2. C++ STL의 스마트 포인터는 shared_ptr에 대한 Custom 소멸자를 지원합니다. 즉, 객체가 파괴될 때 호출되는 함수를 개발자가 직접 지정할 수 있습니다. 반면, 언리얼 엔진4에서는 TSharedPtr의 Custom 소멸자를 지원하지 않습니다.

 

3. C++ STL의 스마트 포인터에는 널 참조를 방지할 수 있는 기능이 없지만, 언리얼 엔진의 TSharedRef은 널 참조를 방지하여 사용할 수 있습니다.


스마트 포인터의 장단점

스마트 포인터의 장점은 일반 포인터와 달리 메모리의 할당과 해제를 자동으로 처리해주기 때문에 실수로 메모리를 해제하지 않아 생기는 메모리 누수를 방지할 수 있다는 점 입니다.

 

반면, 스마트 포인터의 단점은 포인터를 추적하고 참조 계수를 유지하는 작업을 처리하기 때문에 일반적인 포인터보다 추가적인 오버헤드가 발생할 수 있다는 점 입니다.

또한 두 개 이상의 객체가 서로를 참조하는 경우 순환참조의 문제가 발생할 수 있습니다. 이러한 순환참조의 문제는 미리 방지하기 위하여 위크 포인터를 사용하여 객체 간의 의존성을 최소화하도록 권장하고 있습니다. 위크 포인터를 사용하면 객체의 수명 추적에 관여하지 않기 때문에 포인터가 어떠한 객체를 참조하고있어도 메모리를 해제하기 때문입니다.


TSharedFromThis 

언리얼에서는 스마트 포인터를 보다 쉽고 직관적으로 사용할 수 있도록 다양한 헬퍼 클래스와 함수를 제공하는 라이브러리가 있습니다. 그 중 "TSharedFromThis" 클래스를 사용하면 특정 클래스에서 쉐어드 포인터를 만들 때 this 포인터를 쉐어드 포인터로 변환하여 전달할 수 있습니다.

 

쉐어드 포인터는 비침범형 접근자이므로, 쉐어드 포인터가 가르키는 오브젝트는 자신이 스마트 포인터의 소유 하에 있는지 알 수 없습니다. 이때, 오브젝트를 쉐어드 레퍼런스 또는 쉐어드 포인터로서 접근해야 하는 경우가 존재할 때, TSharedFromThis 클래스를 사용하여 오브젝트의 클래스를 파생시키면 됩니다.

 

TSharedFromThis 클래스는 두 가지 함수 AsShared와 SharedThis를 제공합니다. 두 함수를 이용하면 오브젝트를 쉐어드 레퍼런스로 변환하고, 변환된 쉐어드 레퍼런스를 또 쉐어드 포인터로 변환할 수 있습니다.

 

AsShared 함수는 호출되는 오브젝트가 쉐어드 포인터로 관리되고 있는지를 확인하여 이미 쉐어드 포인터로 관리된다면 해당 쉐어드 포인터를 반환하고, 그렇지 않으면 새로운 쉐어드 포인터를 생성하여 반환합니다.

 

SharedThis 함수는 AsShared 함수와 달리 호출되는 오브젝트에 기존에 존재하는 쉐어드 포인터를 반환합니다.

즉, AsShared 함수는 쉐어드 포인터를 생성할 수 있지만, SharedThis 함수는 이미 생성된 쉐이더 포인터만을 가져올 수 있다는 차이점이 있습니다.

 

TSharedFromThis 클래스를 사용할 때의 한 가지 주의할 점은 AsShared나 SharedThis를 생성자로 호출하는 순간, 쉐어드 레퍼런스가 선언되지 않은 상태이기 때문에 충돌이나 assert가 발생하게 된다는 점입니다.


https://docs.unrealengine.com/4.27/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/SmartPointerLibrary/

 

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

위크 포인터 및 Null이 불가능한(non-nullable) 쉐어드 레퍼런스와 같은 쉐어드 포인터들의 커스텀 구현입니다.

docs.unrealengine.com

728x90