Smart Pointer 예제
얕은 / 깊은 복사
언리얼 엔진 Garbage Collection
언리얼 엔진 Reflection System
#include <iostream>
#include <memory> // unique_ptr 사용
using namespace std;
int main() {
// 값 10을 가리키는 unique_ptr 생성
unique_ptr<int> ptr1 = make_unique<int>(10);
// unique_ptr이 관리하는 값 출력
cout << "ptr1의 값: " << *ptr1 << endl;
// unique_ptr은 하나만 존재 가능
// unique_ptr<int> ptr2 = ptr1; // 컴파일 에러 발생!
// 소유권 이동 (move 사용). ptr1은 NULL가리킴
unique_ptr<int> ptr2 = move(ptr1);
cout << "ptr2의 값: " << *ptr2 << endl;
// 범위를 벗어나면 메모리 자동 해제
return 0;
}
using namespace std;
class MyClass {
public:
MyClass(int val) : value(val) {}
void display() const { cout << value << endl;}
private:
int value;
};
int main() {
unique_ptr<MyClass> myclass = make_unique<MyClass>(10); // 생성자 호출
myclass->display();
}
using namespace std;
int main() {
shared_ptr<int> ptr1 = make_shared<int>(10);
cout << ptr1.use_count() << '\n'; // 1
shared_ptr<int> ptr2 = ptr1;
// user_count로 객체를 참조하는 포인터 개수 확인
cout << ptr1.use_count() << '\n'; // 2
cout << ptr2.use_count() << '\n'; // 2
// reset()으로 객체 해제
ptr1.reset();
cout << ptr1 << endl; // 해제되면 NULL 가리킴
cout << ptr1.use_count() << '\n'; // 0
cout << ptr2.use_count() << '\n'; // 1
}
using namespace std;
class MyClass {
public:
MyClass(int val) : value(val) {}
void display() const { cout << value << endl;}
private:
int value;
};
int main() {
shared_ptr<MyClass> ptr1 = make_shared<MyClass>(15);
ptr1->display(); // 15
shared_ptr<MyClass> ptr2 = ptr1;
ptr2->display(); // 15
ptr1.reset(); // shared_ptr 해제
}
weak_ptr은 lock() 호출 후 반환된 shared_ptr 이 유효한지 확인 후에 사용class A {
public:
void say_hello() {
std::cout << "Hello from A\n";
}
};
class B {
public:
std::weak_ptr<A> a_ptr; // A 관찰하는 weak_ptr
void useA() {
if (auto a_shared = a_ptr.lock()) { // lock으로 유효한지 확인
a_shared->say_hello();
} else {
std::cout << "A is no longer available.\n";
}
}
};
int main() {
std::shared_ptr<B> b = std::make_shared<B>();
{
std::shared_ptr<A> a = std::make_shared<A>();
b->a_ptr = a;
b->useA(); // A가 유효하므로 Hello 출력
} // A는 scope을 벗어나며 소멸됨
b->useA(); // A는 소멸되었기에 no longer availabe 출력
}
shared_ptr의 경우 서로가 서로를 가리킬 경우 DeadLock처럼 순환 참조 문제가 생겨 포인터가 해제되지 않음class B; // B를 참조할거라 먼저 전방 선언해줌
class A {
public:
std::shared_ptr<B> b_ptr; // B 소유
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr; // A 소유
~B() { std::cout << "B destroyed\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
} // main 함수가 끝나도 A와 B는 서로 참조 중이라 메모리 해제가 안 됨
weak_ptr로 바꾸어 문제를 해결class B; // Forward declaration
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // weak_ptr로 변경
~B() { std::cout << "B destroyed\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
} // 프로그램 종료되면 정상적으로 해제됨
얕은 복사는 값만을 복사하는 것을 의미
따라서 포인터의 경우도 주소값만을 복사하므로, 새로운 메모리를 할당하여 가리키는 것이 아니라 동일한 메모리를 가리키게 됨
이는 나중에 메모리가 해제될 경우 dangling pointer가 될 위험이 있음


int* A = new int(30);
// 포인터 B가 A가 가리키는 값을 복사 (깊은 복사)
int* B = new int(*A);
언리얼 엔진은 객체들의 메모리 관리를 자동화하기 위해 Garbage Collection System을 사용
Garbage Collection은 "마크 앤 스윕" 알고리즘으로 동작하며, 주기적으로 객체들을 확인하여 프로그램에서 더 이상 사용되지 않는다고 판단되면 메모리에서 제거
이를 통해 메모리 수동 해제의 부담을 덜고, 메모리 누수나 댕글링 포인터 등을 방지
Root Set에서 시작
Mark 단계 - 도달 가능성 분석
Sweep 단계 - 메모리 회수

UObject에는 Garbage Collection Flag가 존재
GUObjectArray라는 전역 배열에 저장된 각 객체 정보의 일부로 관리
RF_RootSet
AddToRoot() 통해 설정, RemoveFromRoot() 통해 해제RF_BeginDestroyed
BeginDestroy()가 호출되었음을 나타냄RF_FinishDestroyed
FinishDestoy()가 호출되었음을 나타냄리플렉션은 UObject를 위한 운영체제
언리얼 엔진 내부에서 동작하는 여러 모듈(가비지 컬렉터, 스크립트 시스템) 등은 모두 UObject 기반
그러나 C++을 이용해 사용자가 정의한 타입들의 경우 엔진에서 알지 못하므로, 이를 위한 작업이 리플렉션

UHT는 C++ 컴파일러가 수행되기 되기 전에 동작하여 C++ 코드 내에서 메타 데이터를 얻고, 내부적으로 소스 코드를 생성
이 동작이 완료된 이후에 C++ 컴파일러가 수행되고 언리얼 엔진에서 UObject로 관리 가능


이러한 매크로를 통해 사용 가능하다
Struct 사용 예시
// FMyStructData.h (or within another .h)
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MyStructData.generated.h"
USTRUCT(BlueprintType) // 블루프린트에서 이 구조체를 타입으로 사용할 수 있도록 함
struct FMyStructData
{
GENERATED_BODY() // UHT가 필요한 코드를 생성하도록 합니다.
public:
// 멤버 변수를 property로 등록
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyStructData")
int32 SampleInt;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyStructData")
FString SampleString;
FMyStructData() : SampleInt(0), SampleString(TEXT("Default")) {}
};
UCLASS(Blueprintable, BlueprintType) // 블루프린트에서 생성 가능하고 타입으로 사용하게 함
class YOURPROJECT_API UMyReflectedObject : public UObject // UObject를 상속받습니다.
{
GENERATED_BODY() // UHT가 필요한 코드를 생성하도록 합니다.
public:
// ... Properties and Functions will go here ...
UMyReflectedObject(); // Constructor
};
