쉐어드 포인터(Shared Pointer) 는 강력한 null 허용 스마트 포인터입니다. 쉐어드 포인터는 메모리 누수, 허상 포인터 및 초기화되지 않은 메모리에 대한 포인터를 방지 등 기본 스마트 포인터의 모든 이점 외에도 다음과 같은 추가 기능도 제공합니다.
-
쉐어드 오너십: 레퍼런스 카운팅을 사용하면 여러 쉐어드 포인터가 참조하는 데이터 오브젝트 중 하나라도 이를 가리키는 한 삭제되지 않도록 할 수 있습니다.
-
자동 인밸리데이션: 변동성 오브젝트를 허상 포인터에 대한 걱정 없이 안전하게 참조할 수 있습니다.
-
위크 레퍼런스: 위크 포인터는 레퍼런스 순환을 깰 수 있습니다.
-
의도 표시: 소유자(쉐어드 레퍼런스 참조)를 관찰자와 구별하고 null 비허용 레퍼런스를 제공합니다.
쉐어드 포인터에는 다음과 같이 주목할 만한 몇 가지 기본 특성이 있습니다.
-
매우 탄탄한 구문
-
비침해적(그러나 리플렉션은 가능함)
-
스레드 세이프(조건부)
-
뛰어난 퍼포먼스, 가벼운 메모리
쉐어드 포인터는 쉐어드 레퍼런스와 유사합니다. 주요 차이점은 쉐어드 레퍼런스가 null을 허용하지 않으므로 항상 유효한 오브젝트를 참조한다는 점입니다. 쉐어드 레퍼런스와 쉐어드 포인터 중에서 선택할 때 비어 있거나 null 허용 오브젝트가 필요하지 않은 한 쉐어드 레퍼런스가 선호 옵션입니다.
선언 및 초기화
쉐어드 포인터는 null을 허용하므로 데이터 오브젝트 사용 여부에 관계없이 초기화할 수 있습니다. 다음은 새 쉐어드 포인터 생성의 몇 가지 예시입니다.
// 빈 쉐어드 포인터를 생성합니다.
TSharedPtr<FMyObjectType> EmptyPointer;
// 새 오브젝트에 쉐어드 포인터를 생성합니다.
TSharedPtr<FMyObjectType> NewPointer(new FMyObjectType());
// 쉐어드 레퍼런스에서 쉐어드 포인터를 생성합니다.
TSharedRef<FMyObjectType> NewReference(new FMyObjectType());
TSharedPtr<FMyObjectType> PointerFromReference = NewReference;
// 스레드 세이프 쉐어드 포인터를 생성합니다.
TSharedPtr<FMyObjectType, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FMyObjectType, ESPMode::ThreadSafe>(MyArgs);
두 번째 예시에서 NodePtr 은 사실상 새로운 FMyObjectType 오브젝트를 소유하는데, 다른 쉐어드 포인터가 해당 오브젝트를 참조하지 않기 때문입니다. 오브젝트를 가리키는 다른 쉐어드 포인터나 쉐어드 레퍼런스 없이 NodePtr 가 범위를 벗어나면 오브젝트가 삭제됩니다.
쉐어드 포인터를 복사하면 시스템은 참조하는 오브젝트에 하나의 레퍼런스를 추가합니다.
// ExistingSharedPointer가 참조하는 오브젝트의 참조 횟수를 늘립니다.
TSharedPtr<FMyObjectType> AnotherPointer = ExistingSharedPointer;
오브젝트는 더 이상 쉐어드 포인터(또는 쉐어드 레퍼런스)가 참조하지 않을 때까지 지속됩니다.
다음과 같이 Reset 함수를 사용하거나 쉐어드 포인터에 null 포인터를 할당하여 쉐어드 포인터를 재설정할 수 있습니다.
PointerOne.Reset();
PointerTwo = nullptr;
// 이제 PointerOne과 PointerTwo가 모두 nullptr를 참조합니다.
MoveTemp (또는 MoveTempIfPossible ) 함수를 사용하면 원래의 쉐어드 포인터를 비워 두고 한 쉐어드 포인터의 콘텐츠를 다른 쉐어드 포인터로 전송할 수 있습니다.
// PointerOne의 콘텐츠를 PointerTwo로 이동합니다. 이후 PointerOne은 nullptr를 참조합니다.
PointerTwo = MoveTemp(PointerOne);
// PointerTwo의 콘텐츠를 PointerOne으로 이동합니다. 이후 PointerTwo는 nullptr를 참조합니다.
PointerOne = MoveTempIfPossible(PointerTwo);
MoveTemp 와 MoveTempIfPossible 은 MoveTemp 에 상수가 아닌 왼쪽 값(lvalue)에서만 실행할 수 있도록 하는 스태틱 어서트가 있다는 점만 다릅니다.
쉐어드 포인터와 쉐어드 레퍼런스 간 변환
쉐어드 포인터와 쉐어드 레퍼런스 간의 변환은 일반적인 작업입니다. 쉐어드 레퍼런스는 암시적으로 쉐어드 포인터로 변환되며 새 쉐어드 포인터가 유효한 오브젝트를 참조한다는 추가 보장을 제공합니다. 변환은 노멀 구문으로 처리됩니다.
TSharedPtr<FMyObjectType> MySharedPointer = MySharedReference;
쉐어드 포인터가 null이 아닌 오브젝트를 참조하는 한 쉐어드 포인터 함수인 ToSharedRef 를 사용하여 쉐어드 포인터에서 쉐어드 레퍼런스를 생성할 수 있습니다. null 쉐어드 포인터에서 쉐어드 레퍼런스를 생성하려고 하면 프로그램이 어서트를 발생시킵니다.
// 잠재적인 어서트를 방지하려면 역참조하기 전에 쉐어드 포인터가 유효한지 확인하세요.
if (MySharedPointer.IsValid())
{
MySharedReference = MySharedPointer.ToSharedRef();
}
비교
쉐어드 포인터가 서로 동일한지 테스트할 수 있습니다. 여기서는 두 쉐어드 포인터가 동일한 오브젝트를 참조하면 같은 것으로 정의됩니다.
TSharedPtr<FTreeNode> NodeA, NodeB;
if (NodeA == NodeB)
{
// ...
}
IsValid 함수와 bool 연산자는 쉐어드 포인터가 유효한 오브젝트를 참조하는지 여부를 설정합니다. 또한 Get 을 호출하여 유효한(null이 아닌) 오브젝트 포인터를 반환하는지 확인할 수도 있습니다.
if (Node.IsValid())
{
// ...
}
if (Node)
{
// ...
}
if (Node.Get() != nullptr)
{
// ...
}
역참조 및 액세스
일반 C++ 포인터와 동일한 방식으로 역참조하고, 메서드를 호출하며, 멤버에 액세스할 수 있습니다. 모든 C++ 포인터와 마찬가지로, 역참조하기 전에는 IsValid 함수를 호출하거나 오버로드된 bool 연산자를 사용하여 null 검사를 수행해야 합니다.
// 역참조 전에 노드가 유효한 오브젝트를 참조하는지 확인합니다.
if (Node)
{
// 다음 세 줄의 코드는 Node를 역참조하고 오브젝트에서 ListChildren을 호출합니다.
Node->ListChildren();
Node.Get()->ListChildren();
(*Node).ListChildren();
}
커스텀 삭제자
쉐어드 포인터 및 쉐어드 레퍼런스는 참조하는 오브젝트에 대한 커스텀 삭제자를 지원합니다. 커스텀 삭제 코드를 실행하려면 다음과 같이 스마트 포인터를 생성할 때 파라미터로 실행하려는 람다 함수를 제공하세요.
void DestroyMyObjectType(FMyObjectType* ObjectAboutToBeDeleted)
{
// 커스텀 삭제 코드 위치입니다.
}
// 이 함수는 커스텀 삭제자가 포함된 스마트 포인터를 생성합니다.
TSharedRef<FMyObjectType> NewReference(new FMyObjectType(), [](FMyObjectType* Obj){ DestroyMyObjectType(Obj); });
TSharedPtr<FMyObjectType> NewPointer(new FMyObjectType(), [](FMyObjectType* Obj){ DestroyMyObjectType(Obj); });