TIL_015 (스마트포인터 예제, 가비지 컬렉션, 리플렉션)

김펭귄·2025년 8월 18일

Today What I Learned (TIL)

목록 보기
15/115

오늘 학습 키워드

  • Smart Pointer 예제

  • 얕은 / 깊은 복사

  • 언리얼 엔진 Garbage Collection

  • 언리얼 엔진 Reflection System

스마트 포인터 예제

Unique Pointer

#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();
}

Shared Pointer

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 Pointer

  • weak_ptrlock() 호출 후 반환된 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; 
}		// 프로그램 종료되면 정상적으로 해제됨

얕은 복사 VS 깊은 복사

얕은 복사

  • 얕은 복사는 값만을 복사하는 것을 의미

  • 따라서 포인터의 경우도 주소값만을 복사하므로, 새로운 메모리를 할당하여 가리키는 것이 아니라 동일한 메모리를 가리키게 됨

  • 이는 나중에 메모리가 해제될 경우 dangling pointer가 될 위험이 있음

깊은 복사

  • 깊은 복사는 새로 메모리 할당받아 복사하는 것으로, 독립적인 메모리를 가지게 되어 안전하다

 	int* A = new int(30);
	// 포인터 B가 A가 가리키는 값을 복사 (깊은 복사)
	int* B = new int(*A);

언리얼 엔진의 메모리 관리

  • 언리얼 엔진은 객체들의 메모리 관리를 자동화하기 위해 Garbage Collection System을 사용

  • Garbage Collection은 "마크 앤 스윕" 알고리즘으로 동작하며, 주기적으로 객체들을 확인하여 프로그램에서 더 이상 사용되지 않는다고 판단되면 메모리에서 제거

  • 이를 통해 메모리 수동 해제의 부담을 덜고, 메모리 누수나 댕글링 포인터 등을 방지

Garbage Collection 작동 방식

  1. Root Set에서 시작

    • 먼저 루트셋에 포함된 객체들을 식별하여 이 객체들은 항상 사용된다고 간주함
    • 게임 엔진 자체, 플레이어 컨트롤러 등이 루트셋에 포함될 수 있다
    • 이러한 객체들은 항상 사용되므로 가비지 컬렉션 대상에서 제외
  2. Mark 단계 - 도달 가능성 분석

    • 루트셋 객체에서 시작해서 직간접적으로 참조하는 UObject를 마크
    • 이는 객체가 사용중임을 표시
  3. Sweep 단계 - 메모리 회수

    • 마크 단계가 끝나면 마크되지 않은 객체들은 회수
    • 이 과정에서 해당 객체의 소멸자가 호출되고 메모리가 반환

Garbage Collection Flag

  • UObject에는 Garbage Collection Flag가 존재

  • GUObjectArray라는 전역 배열에 저장된 각 객체 정보의 일부로 관리

  1. RF_RootSet

    • 객체를 루트셋에 포함시켜 가비지 컬렉션 대상에서 제외
    • AddToRoot() 통해 설정, RemoveFromRoot() 통해 해제
  2. RF_BeginDestroyed

    • 객체 메모리가 해제되어 객체의 BeginDestroy()가 호출되었음을 나타냄
    • 이 함수는 객체가 실제로 메모리에서 해제되기 전에 필요한 정리 작업을 수행하는 함수
  3. RF_FinishDestroyed

    • 객체의 FinishDestoy()가 호출되었음을 나타냄
    • 이 함수는 객체 소멸의 마지막 단계로, 이 함수 호출 후 객체의 메모리가 완전히 해제됨

언리얼 엔진 Reflection System

  • 리플렉션은 UObject를 위한 운영체제

  • 언리얼 엔진 내부에서 동작하는 여러 모듈(가비지 컬렉터, 스크립트 시스템) 등은 모두 UObject 기반

  • 그러나 C++을 이용해 사용자가 정의한 타입들의 경우 엔진에서 알지 못하므로, 이를 위한 작업이 리플렉션

UHT 코드 생성기

  • 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")) {}
};
  • Class 사용 예시
UCLASS(Blueprintable, BlueprintType) // 블루프린트에서 생성 가능하고 타입으로 사용하게 함
class YOURPROJECT_API UMyReflectedObject : public UObject // UObject를 상속받습니다.
{
    GENERATED_BODY() // UHT가 필요한 코드를 생성하도록 합니다.

public:
    // ... Properties and Functions will go here ...
    UMyReflectedObject(); // Constructor
};
  • 리플렉션 정리

profile
반갑습니다

0개의 댓글