Menu
Coddy logo textTech

람다 표현식 심화

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 85번째.

C++11에서 도입된 람다 표현식은 인라인으로 정의할 수 있는 익명 함수입니다. STL 알고리즘과 함께 기본적인 람다를 본 적이 있겠지만, 전체 구문을 이해하면 변수를 캡처하고 변수에 접근하는 방식을 제어하는 강력한 기능을 활용할 수 있습니다.

전체 람다 구문은 다음과 같습니다: [capture](parameters) mutable -> return_type { body }. 캡처 절(capture clause)은 람다가 어떤 외부 변수에 접근할 수 있는지와 그 방법을 결정합니다:

#include <iostream>

int main() {
    int x = 10;
    int y = 20;
    
    auto byValue = [x]() { return x * 2; };        // x의 복사본
    auto byRef = [&y]() { y += 5; };               // y에 대한 참조
    auto allByValue = [=]() { return x + y; };     // 모두 복사
    auto allByRef = [&]() { x++; y++; };           // 모두 참조
    auto mixed = [x, &y]() { y += x; };            // 둘을 혼합
    
    byRef();
    std::cout << y << "\n";  // 25
}

기본적으로 값으로 캡처된 변수들은 람다 내부에서 const입니다. mutable 키워드는 이러한 복사본의 수정을 허용합니다:

int counter = 0;
auto increment = [counter]() mutable {
    return ++counter;  // 람다의 복사본을 수정합니다
};

std::cout << increment() << "\n";  // 1
std::cout << increment() << "\n";  // 2
std::cout << counter << "\n";      // 0 - 원본은 변경되지 않음

C++14에서 초기화 캡처(init captures)가 추가되어, 새로운 변수를 생성하거나 객체를 람다로 이동시킬 수 있게 되었습니다:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // 소유권이 람다로 이전됨

람다는 콜백, 사용자 정의 비교자 또는 이벤트 핸들러와 같이 별도의 함수 객체를 정의하지 않고 동작을 매개변수로 전달해야 할 때 OOP에서 특히 유용합니다.

challenge icon

챌린지

쉬움

다양한 캡처 모드를 사용하는 람다 표현식의 강력함을 보여주는 이벤트 핸들러 시스템을 구축해 보겠습니다. 콜백을 저장하고 호출하는 간단한 이벤트 디스패처를 만들어, 람다가 다양한 방식으로 외부 상태를 캡처하는 방법을 시연하게 됩니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • EventDispatcher.h: 이벤트 콜백을 관리하는 EventDispatcher 클래스를 정의합니다.

    디스패처는 std::function<void()>std::vector를 사용하여 콜백을 저장해야 합니다. 다음 메서드들을 포함하세요:

    • addCallback(std::function<void()> callback) — 리스트에 콜백을 추가합니다.
    • fireAll() — 저장된 모든 콜백을 순서대로 호출합니다.
    • clear() — 모든 콜백을 제거합니다.

    <functional><vector>를 포함해야 합니다.

  • EventDispatcher.cpp: 디스패처의 메서드들을 구현합니다. fireAll() 메서드는 단순히 모든 콜백을 순회하며 각각을 호출해야 합니다.
  • main.cpp: 두 개의 입력을 읽습니다:
    1. 기준 숫자 (정수, base)
    2. 배수 (정수, multiplier)

    EventDispatcher를 생성하고 세 개의 콜백을 추가하여 서로 다른 람다 캡처 기술을 시연합니다:

    1. 기준 숫자를 값에 의한 캡처(by value)로 가져와 Base value: [base]를 출력하는 람다
    2. 배수를 참조에 의한 캡처(by reference)로 가져와 값을 1 증가시킨 후, Multiplier after increment: [multiplier]를 출력하는 람다
    3. 0으로 초기화된 카운터 변수를 값에 의해 캡처하고, 호출될 때마다 이를 증가시키며 Call count: [counter]를 출력하는 mutable 람다

    모든 콜백을 추가한 후, fireAll()을 두 번 호출하여 여러 번의 호출 과정에서 서로 다른 캡처 모드가 어떻게 동작하는지 확인합니다. 두 번의 fireAll() 호출 사이에 구분선으로 ---를 출력하세요.

    마지막으로, 두 라운드가 모두 끝난 후 main 함수에 있는 multiplier 변수의 최종 값을 출력하여 참조 캡처가 변수에 어떤 영향을 주었는지 보여줍니다: Final multiplier: [multiplier]

예를 들어, 입력이 105인 경우:

Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7

입력이 420인 경우:

Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2

주요 동작을 확인해 보세요: 값에 의한 캡처는 원래의 base 값을 변경하지 않고 유지하며, 참조에 의한 캡처는 main의 실제 multiplier 변수를 수정합니다(호출될 때마다 누적됨). 그리고 mutable 람다의 카운터는 매 라운드마다 1을 출력합니다. 이는 fireAll()이 콜백을 값으로 순회하기 때문입니다. 즉, 매 라운드마다 저장된 std::function의 새로운 복사본을 호출하며, 캡처된 카운터는 0에서 시작하여 1로 증가하므로 저장된 콜백 자체의 상태는 진행되지 않습니다.

직접 해보기

#include <iostream>
#include "EventDispatcher.h"

using namespace std;

int main() {
    int base;
    int multiplier;
    cin >> base;
    cin >> multiplier;
    
    EventDispatcher dispatcher;
    
    // TODO: base를 값으로 캡처하는 람다를 추가하세요
    // "Base value: [base]"를 출력해야 합니다
    
    // TODO: multiplier를 참조로 캡처하는 람다를 추가하세요
    // multiplier를 1 증가시킨 후, "Multiplier after increment: [multiplier]"를 출력해야 합니다
    
    // TODO: 0으로 초기화된 카운터를 값으로 캡처하는 MUTABLE 람다를 추가하세요
    // 카운터를 증가시키고 "Call count: [counter]"를 출력해야 합니다
    
    // TODO: 모든 콜백을 호출하기 위해 fireAll()을 호출하세요
    
    // TODO: 구분선으로 "---"를 출력하세요
    
    // TODO: fireAll()을 다시 호출하세요
    
    // TODO: 최종 multiplier 값을 출력하세요: "Final multiplier: [multiplier]"
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨