[HUDWidget.h]
플레이어의 HP와 Exp를 띄우는 UI Widget이었다.
해당 코드 내에 TWeakObjectPtr을 사용했는데
도대체 저게 뭘까??
TWeakObjectPtr을 알기 위해선
해당 포인터가 속한 집단을 알아야 한다.
우선 TWeakObjectPtr은 UE 스마트포인터 클래스에 속해있다.
해당 UE 스마트포인터 클래스는 CPP11 스마트포인터들의 커스텀 구현이라고 한다.
따라서 기존 CPP11의 스마트 포인터부터 알아야,
TWeakObjectPtr의 생성 이유를 알 수 있을 것이다.
아래의 글을 참고하였다
포인터 / 원시포인터 / 스마트포인터가 보인다.
결론부터 말하면, 일반적으로 사용했던 포인터가 원시포인터
(RawPointer)이다.
#include <iostream>
int main() {
int number = 42;
// 1. int 형식을 가리키는 원시 포인터 선언
int* ptr = &number;
// 1-1. ptr을 이용하여 값에 접근
std::cout << "Value of number: " << *ptr << std::endl; // Output: 42
// 1-2. ptr을 이용하여 값 변경
*ptr = 100;
std::cout << "New value of number: " << *ptr << std::endl; // Output: 100
// 2. 동적으로 메모리 할당
int* dynamicPtr = new int;
*dynamicPtr = 777;
std::cout << "Dynamic pointer value: " << *dynamicPtr << std::endl; // Output: 777
// 2-1. 동적으로 할당한 메모리 해제
delete dynamicPtr;
// 2-2. 동적으로 할당된 메모리 해제 후에도 접근 시
//undefined behavior (댕글링 포인터)
std::cout << "After deletion, dynamic pointer value: " << *dynamicPtr << std::endl;
return 0;
}
원시포인터는 그냥 주소를 담는 포인터이다.
( = 일반포인터 로 봐도 무방할 것이다)
1
포인터는 메모리의 스택영역에 저장되는 함수 내에 선언이 되었고,
같이 스택영역인 지역변수 number의 주소값을 받았다.
2
포인터는 메모리의 스택영역에 저장되는 함수 내에 선언이 되었고,
동적으로 할당된 힙메모리의 주소를 받았다.
해당 주소의 값은 수동으로 해제해주어야만 했고, delete를 사용하여 해제한다.
별 문제 없어 보이지만,
공식적으로 원시 포인터를 사용하지 않는 것을 권장한다.
그 이유는 2-2의 댕글링 포인터와 같은 이유 때문이다.
이미 delete한 메모리 공간을 포인터가 계속 가리키고 있는 오류이다.
Raw Pointer는 대표적으로 3가지 오류를 발생시킬 수 있다.
이 중에 메모리누수가 가장 빈번하게 발생하고,
Raw Pointer를 사용해서 발생하는 위의 문제를 해결하기 위해
CPP에선 C에서 없는 스마트포인터 개념을 도입하였다.
void UseRawPointer()
{
// Using a raw pointer -- not recommended.
Song* pSong = new Song(L"Nothing on You", L"Bruno Mars");
// Use pSong...
// Don't forget to delete!
delete pSong;
}
void UseSmartPointer()
{
// Declare a smart pointer on stack
// and pass it the raw pointer.
unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));
// Use song2...
wstring s = song2->duration_;
//...
} // song2 is deleted automatically here.
작성법은 = 을 사용하지 않고,
new를 괄호안에 넣어서 생성한다.
(원시포인터를 생성하고 스마트포인터로 형변환하는 느낌)
https://www.youtube.com/watch?v=DYSEulQoj8Q
기본기능 : 포인터가 더이상 사용되지 않으면 자동으로 메모리 해제
release()
를 사용하면 소유권을 넘길 수 있다reset()
을 사용하여 가리키는 메모리의 값을 delete하고use_count()
를 사용하여 연결된 개수를 알 수 있다shared_ptr과 같이 쓰이기 위한 용도
use_count()
도 사용할 수 있다expired()
를 사용하여 가리키는 대상의 수명이lock()
을 통해서 연결된 shared_ptr을 가져와서weak_ptr
- 강한 참조 = 원래 쓰던 포인터 대입 방식
- use count가 증가
- RawPointer만 Dangling Pointer오류 발생가능
- 약한 참조 = 명시적으로 weak_ptr을 사용하여 약하게 참조한다는 방식
- use count는 증가하지 않음
- Dangling Pointer오류 발생가능
아래의 글을 참고하였다
즉 위의 3가지 포인터는
기존 Native CPP를 위한 포인터라고 보면 되겠다.언리얼은 리플렉션기능을 사용하면(= UPROPERTY 태그를 사용하면)
자동으로 GC시스템에 포함되어 메모리가 관리된다.
따라서 스마트 포인터가 필요 없다고 한다.GC시스템은 shared_ptr과 유사하다
다른점은 GC의 특성상 포인터가 사용되지 않아도
메모리에서 해지되는 타이밍을 정확히 예측할 수 없다.따라서 언리얼 오브젝트의 포인터를 소멸할 때에는 BeginConditionalDestroy()라는 함수를 호출해주고
해지해줄 때까지 그저 기다리는 수 밖에 없다고 한다.
shared_ptr에는 순환-참조 문제가 있었고,
그래서 생겨난 것이 weak_ptr이었다.
마찬가지로 shared_ptr형식인 GC도 순환-참조 문제가 발생하므로
이를 해결하기 위해 등장한게 TWeakObjectPtr
특정 오브젝트를 참조해야하는데,
반드시 소유권이 필요하지 않은 경우
ex) Character는 Running의 기능을 소유하고 있는데
Running이 Character의 위치를 움직이는 작업을 위해서
Character의 멤버에 접근하기 위해 UPROPERTY를 사용한다면,
순환-참조 오류가 발생할 것이다.
따라서 위의 경우에 Running에서 Character를
TWeakObjectPtr로 선언하여 약참조를 걸어주는것이 바람직하다는 말이다.
결론
Native CPP는 (사용자가 만든 일반 클래스)
TUniquePtr/TWeakPtr/TSharedPtr를 사용하여 메모리를 관리한다.
UObject는 UPROPERTY() 혹은 TWeakObjectPtr
둘 중에 하나를 붙여야만 GC가 인식하고 메모리관리가 된다.
https://www.reddit.com/r/unrealengine/comments/osqia3/how_much_should_i_be_using_smart_pointers/
즉 이득우 프로젝트에서 사용했던 코드를 다시보면,
해당 Widget은 StatComponent와 PlayerState에 대해
소유권을 가질 필요가 없어서 약참조를 한 것이다.
아마도 PlayerState에서 위젯을 소유한다면,
문제를 미리 막을 수 있기 때문에 사용하는 것으로 보인다.
하지만 한가지 의문이 남는다
CPP11에서 weak_ptr은 shared_ptr과 같이 사용되어야만 했는데,
어떻게 shared_ptr없이 weak_ptr인 TWeakObjectPtr이 사용되는 걸까??
shared_ptr의 역할을 GC가 대신하고 있는 것으로 생각하고 넘어가려고 한다..
소중한 정보 감사드립니다!