람다 (Lambda)

Jaemyeong Lee·2024년 12월 17일

게임 서버1

목록 보기
100/220

정의

람다란?

  • 람다는 이름 없는 함수 객체(closure object)를 만드는 문법입니다.
  • 즉, "익명 함수"처럼 보이지만 실제로는 operator()를 가진 객체에 가깝습니다.
  • 외부 변수를 캡처해 상태를 들고 다닐 수 있어 STL 알고리즘과 궁합이 좋습니다.

왜 자주 쓰이나?

  • 조건식이 짧을 때 펑터 타입을 따로 만들 필요가 없습니다.
  • 호출 위치 근처에 로직을 붙일 수 있어 문맥 파악이 쉽습니다.
  • find_if, sort, for_each, remove_if에서 특히 자주 등장합니다.

문법 3요소

[capture] (params) { body }
요소설명
capture외부 변수를 람다 안으로 가져오는 방식 (복사/참조)
params매개변수 (일반 함수와 동일)
body구현부. return 타입은 자동 추론. 명시하려면 -> int 등 사용

확장 형태(옵션 포함):

[capture] (params) mutable noexcept -> return_type { body }
  • mutable: 값 캡처 변수를 람다 내부에서 수정 허용
  • noexcept: 예외 비발생 명시
  • -> return_type: 반환형 명시

익명 함수로서의 사용

find_if와 함께

auto it = std::find_if(inventory.begin(), inventory.end(),
    [](const Item& item) { return item.rarity == Rarity::Unique; });
  • IsUniqueItem 같은 펑터를 별도 선언하지 않아도 조건을 바로 표현할 수 있습니다.

반환 타입 명시

auto add = [](int a, int b) -> int { return a + b; };
  • 반환형이 명확하면 생략 가능하고, 복잡한 분기에서는 명시가 안전합니다.

다른 알고리즘 예시

std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b; // 내림차순
});

캡처 (Capture)

캡처 모드

모드문법설명
값 캡처[x], [=]캡처 시점 복사본 사용
참조 캡처[&x], [&]원본 참조 사용
this 캡처[this]객체 포인터(this) 캡처
객체 복사 캡처[*this] (C++17)객체 자체를 값으로 복사
초기화 캡처[p = std::move(ptr)]이름/초기화식을 직접 지정

예시

int wantedId = 2;
auto it = std::find_if(items.begin(), items.end(),
    [wantedId](const Item& item) { return item.id == wantedId; });
auto up = std::make_unique<Job>();
auto task = [job = std::move(up)]() mutable {
    // move 캡처한 unique_ptr 사용
};

실무 권장:

  • 기본 캡처([=], [&])보다 개별 캡처를 우선 사용
  • 수명에 민감한 코드(비동기, 지연 실행)에서는 참조 캡처를 특히 조심

위험: 참조 캡처 & 객체 수명

MakeResetHPJob 예

  • Knight*를 참조 캡처한 Job을 만들고 Knight가 먼저 파괴되면,
    Job 실행 시 댕글링 참조/포인터 접근이 발생합니다.

복사 캡처도 주의

  • 멤버 함수 안에서 [=]를 쓰고 멤버에 접근하면, 실제로는 this 포인터에 의존합니다.
  • 따라서 객체 수명이 람다 실행보다 짧으면 동일하게 위험합니다.
  • 멤버 값을 안전하게 복사하려면 로컬 변수로 뽑아 캡처하는 방식이 명확합니다.
int hpSnapshot = hp_; // 멤버 값을 로컬로 복사
auto fn = [hpSnapshot]() {
    // this 없이 안전하게 사용
};

안전한 사용

  • 필요한 변수만 개별 캡처합니다.
  • 지연 실행/비동기 작업에서는 값 캡처를 기본으로 고려합니다.
  • 객체 참조가 꼭 필요하면 수명 보장 전략(소유권, shared_ptr/weak_ptr)을 같이 설계합니다.

예: weak_ptr 패턴

std::weak_ptr<Knight> wk = knightShared;
auto job = [wk]() {
    if (auto sk = wk.lock()) {
        sk->ResetHp();
    }
};

find_if와의 궁합

  • find_if의 Predicate은 짧고 맥락 의존적이라 람다로 쓰면 가독성이 높습니다.
  • "검색 기준이 호출 위치 근처에 보인다"는 점이 유지보수에 큰 장점입니다.
int wantedId = 42;
auto it = std::find_if(items.begin(), items.end(),
    [wantedId](const Item& item) { return item.id == wantedId; });

람다 사용 체크포인트

  • 캡처가 최소화되어 있는가?
  • 반환 타입/파라미터 타입이 명확한가?
  • 람다가 너무 길어졌다면 named function/펑터로 분리할 시점은 아닌가?

profile
李家네_공부방

0개의 댓글