2025/11/27 TIL

민트맛치킨·2025년 11월 27일

Unreal

목록 보기
24/26

C++ 람다

람다(Lambda)는 이름 없는 함수 객체로 함수를 변수처럼 다룰 수 있게 해줌

// 기본 문법
[캡처](매개변수) -> 반환타입 { 함수 본문 }

// 예시
auto add = [](int a, int b) -> int { return a + b; };
int result = add(3, 5);  // 8

캡처(Capture)

람다 외부의 변수를 람다 내부에서 사용할 수 있게 가져오는 것

int x = 10;
int y = 20;

// 값으로 캡처 (복사)
auto lambda1 = [x]() { return x * 2; };

// 참조로 캡처
auto lambda2 = [&x]() { x = 100; };

// 여러 변수 캡처
auto lambda3 = [x, &y]() { y = x + 1; };

캡처 종류

캡처 방식설명예시
[x]x를 값으로 캡처 (복사)원본 변경 안 됨
[&x]x를 참조로 캡처원본 변경됨
[=]모든 변수를 값으로 캡처전역 캡처
[&]모든 변수를 참조로 캡처전역 캡처
[this]현재 객체의 this 포인터 캡처멤버 접근 가능

전역 캡처의 문제

의도치 않은 변수 캡처

void processData() {
    int Value = 100;
    int temp = 0;           	 // 임시 변수
    int debugCounter = 0;        // 디버깅용
    std::string logMessage = ""; // 로그용
    
    // [=] 사용, 모든 변수가 캡처됨
    auto lambda = [=]() {
        return Value * 2;
    };
    
    // 실제로 필요한 건 Value 하나뿐인데
    // temp, debugCounter, logMessage까지 모두 복사됨
    // 메모리 낭비
}

댕글링 참조 (Dangling Reference)

std::function<int()> createLambda() {
    int localValue = 2;
    
    // [&] 참조 캡처
    return [&]() {
        return localValue;  // localValue는 이미 파괴됨
    };
}

void danger() {
    auto lambda = createLambda();
    int result = lambda();  // 정의되지 않은 동작 (Undefined Behavior)
}

클래스에서의 댕글링

class Widget {
    int m_id = 1;
    
    void setupCallback() {
        // [=]는 this를 캡처함
        auto callback = [=]() {
            return m_id;  // this->m_id 접근
        };
        
        // Widget이 파괴되면 callback은 댕글링 this를 가짐
    }
};

언리얼 엔진에서의 람다와 댕글링 문제

타이머와 this 캡처

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    // 위험
    GetWorld()->GetTimerManager().SetTimer(
        TimerHandle,
        [this]()
        {
            // 3초 후 실행되는데 이 시점에 Actor가 이미 파괴되었다면 크래시
            DoSomething();
        },
        3.0f,
        false
    );
}

해결 방법 1: TWeakObjectPtr 사용

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    // TWeakObjectPtr로 약한 참조 생성
    TWeakObjectPtr<AMyActor> WeakThis(this);
    
    GetWorld()->GetTimerManager().SetTimer(
        TimerHandle,
        [WeakThis]()
        {
            // 유효성 검사
            if (WeakThis.IsValid())
            {
                WeakThis->DoSomething();
            }
        },
        3.0f,
        false
    );
}

해결 방법 2: FTimerDelegate::CreateWeakLambda

객체가 파괴되면 자동으로 타이머가 실행되지 않음

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    FTimerDelegate TimerDelegate = FTimerDelegate::CreateWeakLambda(
        this,  // UObject를 상속받은 객체
        [this]()
        {
            // this가 유효할 때만 실행됨
            DoSomething();
        }
    );
    
    GetWorld()->GetTimerManager().SetTimer(
        TimerHandle,
        TimerDelegate,
        3.0f,
        false
    );
}

해결 방법 3: SetTimer의 WeakLambda 오버로드

void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    // 간결한 방법
    GetWorld()->GetTimerManager().SetTimer(
        TimerHandle,
        FTimerDelegate::CreateWeakLambda(this, [this]()
        {
            DoSomething();
        }),
        3.0f,
        false
    );
}

해결 방법 4: BindWeakLambda (델리게이트용)

델리게이트에 람다를 바인딩할 때 사용

// 싱글캐스트 델리게이트
DECLARE_DELEGATE(FMyDelegate);

void AMyActor::SetupDelegate()
{
    FMyDelegate MyDelegate;
    
    // BindWeakLambda 사용
    MyDelegate.BindWeakLambda(this, [this]()
    {
        // this가 유효할 때만 실행됨
        HandleCallback();
    });
}

해결 방법 5: 멀티캐스트 델리게이트에서 AddWeakLambda

// 멀티캐스트 델리게이트
DECLARE_MULTICAST_DELEGATE(FMyMulticastDelegate);

FMyMulticastDelegate OnSomethingHappened;

void AMyActor::BindToDelegate()
{
    OnSomethingHappened.AddWeakLambda(this, [this]()
    {
        HandleEvent();
    });
}

메서드 비교

메서드용도사용 예시
FTimerDelegate::CreateWeakLambda타이머 델리게이트 생성타이머 콜백
BindWeakLambda싱글캐스트 델리게이트 바인딩일반 델리게이트
AddWeakLambda멀티캐스트 델리게이트에 추가이벤트 구독
TWeakObjectPtr + IsValid() 체크범용다른 Actor 참조

0개의 댓글