특정 "범위(Scope)"에 바인딩된 싱글톤 객체
원하는 구간에서만 존재하여 데이터나 로직을 만들고 싶을 때 사용
UGameInstanceSubsystem, UWorldSubsystem를 가장 많이 사용
UEngineSubsystem
가장 긴 수명을 가지는 subsystem
엔진이 켜지는 순간부터 종료될 때까지 존재
게임 시작 안 하고 에디터만 켜져있어도 존재함
게임 개발보다는 분석, 로그 시스템에 주로 사용
class UMyEngineSubsystem : public UEngineSubsystem
{};
UEditorSubsystem
에디터 전용으로, 패키지하면 포함 안 됨
에디터 플러그인, 에셋 처리 등 에디터 커스터마이징에 사용
class UMyEditorSubsystem : public UEditorSubsystem
{};
UGameInstanceSubsystem
게임 인스턴스와 수명 동일
게임 시작 시 생성, 종료 시 끝나며 레벨이 전환되도 유지됨
저장/업적/인벤토리 시스템 등 레벨 바뀌어도 유지되어야 하는 데이터나 로직에 사용
class UMyGameInstanceSubsystem : public UGameInstanceSubsystem
{};
UWorldSubsystem
월드와 수명 동일
레벨 생성 시 생성되고, 레벨 종료되면 죽음 (레벨마다 별도의 인스턴스)
웨이브 시스템, AI 매니저 등에 사용
class UMyWorldSubsystem : public UWorldSubsystem
{};
ULocalPlayerSubsystem
로컬 플레이어와 수명 동일
플레이어마다 별도의 인스턴스가 생성됨
플레이어마다 가지는 UI, Input Manager, Camera 설정 등 개인 설정에 사용
class UMyLocalPlayerSubsystem : public ULocalPlayerSubsystem
{};
// MyGameInstanceSubsystem.h
UCLASS()
class UMyGameInstanceSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// 이 두 함수는 꼭 override 해주기
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
};
// MyGameInstanceSubsystem.cpp
void UMyGameInstanceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Log, TEXT("Save System Initialized!"));
}
void UMyGameInstanceSubsystem::Deinitialize()
{
UE_LOG(LogTemp, Log, TEXT("Save System Shutting Down!"));
Super::Deinitialize();
}
Initialize : 액터의 BeginPlay같은 것으로, 초기화 역할해주는 함수
Deinitialize : EndPlay같은 함수
// GameInstanceSubsystem
UMySaveSubsystem* SaveSys = GetGameInstance()->GetSubsystem<UMySaveSubsystem>();
SaveSys->SaveGame();
// WorldSubsystem
UMyWaveSubsystem* WaveSys = GetWorld()->GetSubsystem<UMyWaveSubsystem>();
WaveSys->StartNextWave();
// LocalPlayerSubsystem
UMyHUDSubsystem* HUDSys = GetLocalPlayer()->GetSubsystem<UMyHUDSubsystem>();
HUDSys->ShowPauseMenu();
서브 시스템 사용하려면 각 서브시스템의 주인을 통해 접근
싱글톤 객체이므로, GetSubsystem으로 쉽게 인스턴스 받아 사용 가능
엔진이 알아서 생명주기에 맞게 초기화와 소멸시켜줌
클라와 서버 각자 생기므로, 동기화하고 싶으면 RPC통해서 직접 해주어야 함.
자동으로 동기화 안 됨
이름이 없는 함수객체를 즉석에서 정의하고 사용하게 해주는 기능
별도의 함수 정의 없이 필요한 곳에서 바로 작성할 수 있어 코드의 흐름을 방해하지 않음
[Capture Clause] (Parameters) -> ReturnType { Body }
Capture Clause (캡처 절): 외부 변수를 람다 내부(객체 내부)로 가져옴
Parameters (매개변수): 일반 함수와 동일한 인자 리스트
ReturnType (반환 타입): 생략 시 컴파일러가 추론
람다를 보고 컴파일러가 익명의 클래스를 생성
캡처 절에 있는 변수는 이 클래스의 멤버변수가 됨
람다의 매개변수와 함수부는 이 클래스의 operator() 연산자 오버로딩 되어 정의됨
이렇게 생성된 인스턴스를 Closure라고 부름
캡처 절은 객체 내부에서 값으로 존재하게 되고, 매개인자는 외부에서 넣어주는 값으로 차이가 존재함
int limit = 10; // 외부 변수
auto checkLimit = [limit](int n) {
// limit: 임시 객체에 멤버변수로 존재하게 됨. 항상 매개변수로 넣어줄 필요 없음
// n: 매개변수 (함수를 쓸 때마다 바뀔 숫자)
return n > limit;
};
checkLimit(5); // false (5는 n에 들어감)
checkLimit(15); // true (15는 n에 들어감)
[] : 외부 변수를 캡처하지 않음
[=] : 모든 외부 변수를 값에 의한 복사(Copy)로 캡처 (읽기 전용)
[&] : 모든 외부 변수를 참조(Reference)로 캡처
[x, &y]
x는 const로 임시 객체 내부로 값 복사, y는 참조로 객체 내부로 가져옴
외부에서 x를 변경하더라고, 임시 객체 내부에선 값 변경 안 됨. 복사한 것이기 때문
외부에서 y를 변경하면 내부도 변경됨. 참조자이기 때문
내부에서 y변경하면, 외부도 변경됨(참조자)
내부에서 x를 수정하면 컴파일 에러 발생함. 변수와, 함수가const이기 때문
그래서 mutable키워드를 사용하면, 내부에서x값을 변경 가능함 (외부 x는 변화 없음)
int count = 0;
// mutable이 없으면 count++ 에서 컴파일 에러 발생
auto increment = [count]() mutable {
count++; // 람다 내부의 복사본 count를 수정
std::cout << "Inside: " << count << std::endl;
};
increment(); // Inside: 1
increment(); // Inside: 2
std::cout << "Outside: " << count << std::endl; // Outside: 0 (원본은 그대로)
auto getLambda() {
int local_val = 10;
return [&local_val]() { return local_val + 5; };
} // 함수가 종료되면서 local_val은 메모리에서 사라짐!
auto myLambda = getLambda();
myLambda(); // 에러! 이미 사라진 local_val의 메모리에 접근 (Undefined Behavior)
값 복사 캡쳐를 이용하여 해결 (복사비용과 객체크기 커지면 안 좋다는 단점 존재)
스마트 포인터이용해 캡쳐
auto p = std::make_shared<int>(42);
auto lambda = [p]() { // p의 참조 횟수(Reference Count)가 증가하여 안전함
std::cout << *p << std::endl;
};
list 자료구조는 다른 컨테이너들과 다르게 <algorithm>의 std::sort를 사용하지 못 함
대신에 자신의 멤버변수 sort를 이용
std::sort는 랜덤 액세스 반복자로 접근이 가능한 컨테이너이어야 함
하지만, list의 경우 랜덤 액세스가 불가능하고, 양방향 반복자만 지원하여서 std::sort가 사용이 불가능하다
std::sort는 요소의 값을 직접 복사하거나, 교환시켜 정렬시킴 (데이터가 크면 비용이 크다는 단점 존재)
list::sort는 값의 복사, 이동 없이 노드 사이의 포인터만 바꿔주는 방식으로 정렬을 함
std::sort를 사용 못하는 이유는 아니지만, list::sort는 실제 데이터는 메모리의 위치에 그대로 있고, 전/후의 연결정보만 바꾸면서 정렬하기에 매우 효율적인 정렬 방법이다
std::sort는 보통 퀵소트를 사용하지만, list::sort는 병합 정렬 방식을 채택
병합 정렬은 동일한 값의 순서가 유지되는 안정 정렬
퀵소트는 피벗을 이용한 분할 정복 알고리즘
피벗 선택: 배열에서 원소 하나를 고릅니다. (보통 중간)
분할(Partition): 피벗을 기준으로 작은 데이터는 왼쪽, 큰 데이터는 오른쪽으로 이동. 이 과정이 끝나면 피벗은 자기의 최종 정렬 위치에 고정
재귀(Recursion): 피벗을 제외한 왼쪽 부분과 오른쪽 부분에 대해 반복