Menu
Coddy logo textTech

람다 표현식 심화

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

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

완전한 람다 구문은 다음과 같습니다: [capture](parameters) mutable -> return_type { body }. 캡처 절은 람다가 어떤 외부 변수를 어떤 방식으로 액세스할 수 있는지 결정합니다:

#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에서는 초기화 캡처가 추가되어 새로운 변수를 만들거나 객체를 람다로 이동할 수 있습니다:

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

별도의 함수 객체를 정의하지 않고 동작을 매개변수로 전달해야 할 때(예: callback, 사용자 지정 비교자 또는 이벤트 처리기) 람다는 특히 OOP에서 유용합니다.

challenge icon

챌린지

쉬움

서로 다른 캡처 모드를 사용하여 람다 표현식의 강력한 기능을 보여 주는 이벤트 처리 시스템을 만들어 봅시다. 콜백을 저장하고 호출하는 간단한 이벤트 디스패처를 만들면서, 람다가 다양한 방식으로 외부 상태를 캡처하는 방법을 살펴봅니다.

코드를 세 개의 파일로 구성합니다:

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

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

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

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

  • EventDispatcher.cpp: 디스패처의 메서드를 구현합니다. fireAll() 메서드는 모든 콜백을 순회하며 각 콜백을 호출하기만 하면 됩니다.
  • main.cpp: 두 개의 입력을 읽습니다:
    1. 기본 숫자(정수)
    2. 승수(정수)

    EventDispatcher를 생성하고 세 개의 콜백을 추가하여 서로 다른 람다 캡처 기법을 보여 주세요:

    1. 기본 숫자를 값으로 캡처하고 다음을 출력하는 람다: Base value: [base]
    2. 승수를 참조로 캡처하고, 1만큼 증가시킨 다음 다음을 출력하는 람다: Multiplier after increment: [multiplier]
    3. 카운터 변수를 (0으로 초기화하여) 값으로 캡처하고, 호출될 때마다 증가시키며 다음을 출력하는 mutable 람다: Call count: [counter]

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

    마지막으로 두 라운드가 끝난 후, 참조 캡처가 어떤 영향을 주었는지 보여 주기 위해 main의 승수 변수의 최종 값을 출력합니다: 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 람다의 counter는 매 라운드마다 1을 읽습니다. 이는 fireAll()이 콜백을 값으로 순회하기 때문입니다. 각 라운드에서는 저장된 std::function의 새로운 복사본이 호출되고, 이 복사본의 캡처된 counter는 0에서 시작하여 1로 증가하므로 저장된 콜백 자체의 상태는 결코 진행되지 않습니다.

직접 해보기

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

using namespace std;

int main() {
    int base;
    int multiplier;
    cin >> base;
    cin >> multiplier;
    
    EventDispatcher dispatcher;
    
    // TODO: base를 BY VALUE로 캡처하는 람다를 추가하세요
    // 다음과 같이 출력해야 합니다: "Base value: [base]"
    
    // TODO: multiplier를 BY REFERENCE로 캡처하는 람다를 추가하세요
    // It should increment multiplier by 1, then print: "Multiplier after increment: [multiplier]"
    
    // TODO: counter(0으로 초기화)를 값으로 캡처하는 MUTABLE 람다를 추가하세요
    // counter를 증가시키고 다음과 같이 출력해야 합니다: "Call count: [counter]"
    
    // TODO: 모든 콜백을 호출하기 위해 fireAll()을 호출하세요
    
    // TODO: 구분자로 "---"를 출력하세요
    
    // TODO: fireAll()을 다시 호출하세요
    
    // TODO: Print the final multiplier value: "Final multiplier: [multiplier]"
    
    return 0;
}
quiz icon실력 점검

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

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

직접 연습해 보세요: 온라인 C++ 컴파일러