icon안동민 개발노트

람다 표현식


람다 표현식의 개념과 기본 구조

 람다 표현식은 C++ 11에서 도입된 기능으로, 익명 함수를 생성하는 간결한 방법을 제공합니다. 이는 함수형 프로그래밍의 개념을 C++에 도입한 것으로, 코드를 더 간결하고 표현력 있게 만들어줍니다.

 람다 표현식의 기본 구조는 다음과 같습니다.

[capture clause] (parameters) -> return_type { function body }
  • [capture clause] : 람다 함수 외부의 변수를 캡처하는 방법을 지정
  • (parameters) : 함수의 매개변수 (선택적)
  • -> return_type : 반환 타입 (선택적, 대부분의 경우 컴파일러가 추론 가능)
  • { function body } : 함수의 본문

 예를 들어, 다음은 두 정수를 더하는 간단한 람다 표현식입니다.

auto add = [](int a, int b) { return a + b; };

캡처 절 (Capture Clause)

 캡처 절은 람다 함수가 외부 범위의 변수를 어떻게 "캡처"할지 지정합니다. 주요 캡처 방식은 다음과 같습니다.

  • [] : 아무것도 캡처하지 않음
  • [=] : 모든 외부 변수를 값으로 캡처
  • [&] : 모든 외부 변수를 참조로 캡처
  • [x] : x를 값으로 캡처
  • [&x] : x를 참조로 캡처
  • [=, &x] : x만 참조로 캡처하고 나머지는 값으로 캡처
  • [&, x] : x만 값으로 캡처하고 나머지는 참조로 캡처
예제
#include <iostream>
 
int main() {
    int x = 10;
    int y = 20;
 
    auto lambda1 = [x, &y]() {
        // x는 값으로 캡처되어 변경 불가
        // y는 참조로 캡처되어 변경 가능
        y += 5;
        return x + y;
    };
 
    std::cout << "Result: " << lambda1() << std::endl; // 출력: Result: 35
    std::cout << "y is now: " << y << std::endl; // 출력: y is now: 25
 
    return 0;
}

람다와 STL 알고리즘

 람다 표현식은 STL 알고리즘과 함께 사용될 때 특히 강력합니다. 예를 들어, std::sort를 사용할 때 람다를 통해 사용자 정의 비교 함수를 쉽게 제공할 수 있습니다.

#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> numbers = {4, 1, 3, 5, 2};
 
    // 내림차순 정렬
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) { return a > b; });
 
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl; // 출력: 5 4 3 2 1
 
    return 0;
}

제네릭 람다 (C++ 14)

 C++ 14부터는 람다의 매개변수 타입을 auto로 지정할 수 있어, 제네릭 람다를 만들 수 있습니다. 이를 통해 타입에 독립적인 람다를 작성할 수 있습니다.

#include <iostream>
#include <vector>
#include <string>
 
int main() {
    auto print = [](const auto& container) {
        for (const auto& item : container) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    };
 
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::vector<std::string> words = {"Hello", "Generic", "Lambda"};
 
    print(numbers); // 출력: 1 2 3 4 5
    print(words);   // 출력: Hello Generic Lambda
 
    return 0;
}

람다와 클로저

 람다 표현식은 클로저를 생성합니다. 클로저는 함수와 그 함수가 참조하는 환경을 포함하는 객체입니다. 이를 통해 함수가 정의된 환경의 상태를 "기억"할 수 있습니다.

#include <iostream>
#include <functional>
 
std::function<int(int)> makeMultiplier(int factor) {
    return [factor](int x) { return x * factor; };
}
 
int main() {
    auto times2 = makeMultiplier(2);
    auto times3 = makeMultiplier(3);
 
    std::cout << "5 * 2 = " << times2(5) << std::endl; // 출력: 5 * 2 = 10
    std::cout << "5 * 3 = " << times3(5) << std::endl; // 출력: 5 * 3 = 15
 
    return 0;
}

mutable 람다

 기본적으로 람다는 값으로 캡처된 변수를 변경할 수 없습니다. mutable 키워드를 사용하면 이를 가능하게 할 수 있습니다.

#include <iostream>
 
int main() {
    int x = 10;
 
    auto lambda = [x]() mutable {
        x += 5;
        return x;
    };
 
    std::cout << lambda() << std::endl; // 출력: 15
    std::cout << lambda() << std::endl; // 출력: 20
    std::cout << "Original x: " << x << std::endl; // 출력: Original x: 10
 
    return 0;
}

람다의 성능 고려사항

 람다는 일반적으로 인라인화되어 함수 포인터나 std::function보다 더 효율적입니다. 하지만 캡처하는 변수가 많아지면 성능에 영향을 줄 수 있습니다. 대규모 객체를 캡처할 때는 참조 캡처를 고려해볼 수 있습니다.

C++ 17 이후의 람다 기능

  • C++ 17 : 람다 캡처에서 *this의 복사 캡처 가능
  • C++ 20 : 템플릿 람다, 캡처되지 않은 람다를 함수 포인터로 변환 가능

실습 : 학생 성적 처리 시스템

 학생들의 성적 정보를 처리하는 시스템을 람다를 활용하여 구현해보세요.

  1. 학생 구조체 정의 (이름, 점수)
  2. 학생들의 벡터 생성
  3. 람다를 사용하여 다음 작업 수행
    • 점수에 따라 학생 정렬
    • 특정 점수 이상인 학생 필터링
    • 모든 학생의 평균 점수 계산
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
 
struct Student {
    std::string name;
    int score;
};
 
int main() {
    std::vector<Student> students = {
        {"Alice", 85}, {"Bob", 70}, {"Charlie", 90}, {"David", 80}
    };
 
    // 점수에 따라 정렬
    std::sort(students.begin(), students.end(), 
              [](const Student& a, const Student& b) { return a.score > b.score; });
 
    std::cout << "Sorted students:" << std::endl;
    for (const auto& student : students) {
        std::cout << student.name << ": " << student.score << std::endl;
    }
 
    // 80점 이상인 학생 필터링
    auto highScorers = std::count_if(students.begin(), students.end(), 
                                     [](const Student& s) { return s.score >= 80; });
 
    std::cout << "\nNumber of students with score 80 or above: " << highScorers << std::endl;
 
    // 평균 점수 계산
    auto totalScore = std::accumulate(students.begin(), students.end(), 0,
                                      [](int sum, const Student& s) { return sum + s.score; });
    double averageScore = static_cast<double>(totalScore) / students.size();
 
    std::cout << "Average score: " << averageScore << std::endl;
 
    return 0;
}

연습 문제

  1. 주어진 벡터에서 짝수만 필터링하여 새로운 벡터를 반환하는 람다 함수를 작성하세요.
  2. 문자열 벡터를 입력받아 각 문자열의 길이를 기준으로 오름차순 정렬하는 람다 함수를 작성하세요.


참고 자료

  • C++ 레퍼런스의 람다 표현식 문서 : Lambda expressions
  • "Effective Modern C++" by Scott Meyers의 람다 관련 항목
  • CppCon 발표 : "C++ Lambda Idioms" by Scott Meyers
  • "C++ Templates : The Complete Guide" by David Vandevoorde, Nicolai M. Josuttis, and Douglas Gregor (제네릭 람다와 관련된 고급 주제)