람다 함수는 기본적으로 상수로 캡처한 변수를 수정할 수 없습니다. 이를 해결하기 위해 mutable 키워드를 사용하여 람다 함수를 변경할 수 있게 만듭니다.
람다와 mutable 람다의 차이
기본 람다
기본적으로 람다 함수는 캡처된 변수를 상수로 취급합니다. 즉, 람다 함수 내에서 캡처된 변수를 수정할 수 없습니다.
int x = 0;
auto lambda = [x]() {
// x++; // 오류: x는 상수로 캡처되었기 때문에 수정할 수 없습니다.
std::cout << x << std::endl;
};
lambda();
mutable 람다
mutable 키워드를 사용하면, 람다 함수 내에서 캡처된 변수를 수정할 수 있습니다. mutable 키워드를 사용하면 캡처된 변수는 상수가 아닌 일반 변수로 취급됩니다.
int x = 0;
auto lambda = [x]() mutable {
x++; // 가능: mutable 키워드를 사용했기 때문에 x를 수정할 수 있습니다.
std::cout << x << std::endl;
};
lambda();
mutable 람다의 예제
mutable 람다는 캡처된 변수를 람다 함수 내부에서 변경할 수 있도록 합니다. 이 기능을 사용할 때는 다음과 같은 상황을 고려해야 합니다.
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int factor = 2;
// 기본 람다: factor를 수정할 수 없음
std::for_each(numbers.begin(), numbers.end(), [factor](int &n) {
// factor++; // 오류: factor는 상수로 캡처되었기 때문에 수정할 수 없음
n *= factor;
});
// mutable 람다: factor를 수정할 수 있음
std::for_each(numbers.begin(), numbers.end(), [factor]() mutable {
factor++; // 가능: mutable 키워드를 사용했기 때문에 factor를 수정할 수 있음
});
// 결과 출력
for (int n : numbers) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
이 예제에서는 factor 변수를 mutable 키워드를 사용하여 람다 함수 내부에서 수정할 수 있게 했습니다.
Unreal Engine에서의 mutable 람다 사용
Unreal Engine에서 람다 함수와 FTimerManager를 사용하여 타이머 콜백을 설정할 때도 mutable 키워드를 사용할 수 있습니다. 특히, 타이머 콜백 내에서 상태를 변경해야 할 경우 유용합니다.
Unreal Engine 예제
다음은 mutable 람다를 사용하여 타이머 콜백 내에서 캡처된 변수를 수정하는 예제입니다.
#include "YourClass.h"
#include "Engine/World.h"
#include "TimerManager.h"
// Sets default values
AYourClass::AYourClass()
{
PrimaryActorTick.bCanEverTick = true;
// Initialize variables
SequentialIndex = 0;
DelayBetweenSequentialSpawns = 0.5f;
TimeBetweenSpawns = 5.0f; // Example value
SpawnStyle = ESpawnStyle::Sequential; // Default value
}
// Called when the game starts or when spawned
void AYourClass::BeginPlay()
{
Super::BeginPlay();
// Start the sequential spawn
StartSequentialSpawn();
}
// Called every frame
void AYourClass::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AYourClass::StartSequentialSpawn()
{
// Define the initial spawn start callback using FTimerDelegate
FTimerDelegate SequentialSpawnStartCallback;
SequentialSpawnStartCallback = FTimerDelegate::CreateLambda([this]()
{
switch (SpawnStyle)
{
case ESpawnStyle::Sequential:
SequentialIndex = 0;
SequentialSpawn();
break;
case ESpawnStyle::ReverseSequential:
ReverseIndex = SelectedPointIndexList.Num() - 1;
ReverseSequentialSpawn();
break;
case ESpawnStyle::MiddleOut:
MiddleOutSpawn();
break;
default:
UE_LOG(LogTemp, Warning, TEXT("Unsupported spawn style"));
break;
}
});
// Start the timer using the initial callback
GetWorld()->GetTimerManager().SetTimer(SpawnTimer, SequentialSpawnStartCallback, TimeBetweenSpawns, false);
}
void AYourClass::SequentialSpawn()
{
if (SequentialIndex < SelectedPointIndexList.Num())
{
SelectedPointIndex = SelectedPointIndexList[SequentialIndex];
SpawnEnemy();
SequentialIndex++;
// Set a timer to call this function again after a delay
FTimerDelegate SequentialSpawnCallback;
SequentialSpawnCallback.BindLambda([this]()
{
SequentialSpawn();
});
GetWorld()->GetTimerManager().SetTimer(SequentialTimer, SequentialSpawnCallback, DelayBetweenSequentialSpawns, false);
}
else
{
// All spawns are done, wait for the next cycle
StartSequentialSpawn();
}
}
void AYourClass::ReverseSequentialSpawn()
{
if (ReverseIndex >= 0)
{
SelectedPointIndex = SelectedPointIndexList[ReverseIndex];
SpawnEnemy();
ReverseIndex--;
// Set a timer to call this function again after a delay
FTimerDelegate ReverseSpawnCallback;
ReverseSpawnCallback.BindLambda([this]()
{
ReverseSequentialSpawn();
});
GetWorld()->GetTimerManager().SetTimer(SequentialTimer, ReverseSpawnCallback, DelayBetweenSequentialSpawns, false);
}
else
{
// All spawns are done, wait for the next cycle
StartSequentialSpawn();
}
}
void AYourClass::MiddleOutSpawn()
{
int32 MidIndex = SelectedPointIndexList.Num() / 2;
int32 LeftIndex = MidIndex - 1;
int32 RightIndex = MidIndex;
auto MiddleOutCallback = [this, LeftIndex, RightIndex]() mutable
{
if (RightIndex < SelectedPointIndexList.Num())
{
SelectedPointIndex = SelectedPointIndexList[RightIndex];
SpawnEnemy();
RightIndex++;
}
if (LeftIndex >= 0)
{
SelectedPointIndex = SelectedPointIndexList[LeftIndex];
SpawnEnemy();
LeftIndex--;
}
// Set a timer to call this function again after a delay
FTimerDelegate MiddleOutSpawnCallback;
MiddleOutSpawnCallback.BindLambda([this, LeftIndex, RightIndex]() mutable
{
MiddleOutSpawn();
});
GetWorld()->GetTimerManager().SetTimer(SequentialTimer, MiddleOutSpawnCallback, DelayBetweenSequentialSpawns, false);
};
// Start the middle-out spawn sequence
MiddleOutCallback();
}
이 예제에서는 mutable 키워드를 사용하여 MiddleOutCallback 람다 함수 내에서 LeftIndex와 RightIndex 변수를 수정할 수 있도록 했습니다. FTimerDelegate와 BindLambda를 사용하여 언리얼 스타일로 람다 함수를 정의하고 타이머 콜백을 설정했습니다.
'개발이야기 > 언리얼 c++' 카테고리의 다른 글
언리얼 C++ to BP: BlueprintNativeEvent 매크로로 C++함수 오버라이드 하기 (0) | 2024.06.18 |
---|---|
언리얼 C++: EnhancedInput 시스템이란? (0) | 2024.06.18 |
언리얼C++: ConstructorHelpers를 사용해 에셋을 로딩해보자 (0) | 2024.06.18 |
unreal VR 패키징 Pico와 Oculus (0) | 2024.06.05 |
언리얼 C++: TScriptInterface (0) | 2024.05.28 |