본문 바로가기
GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~

[C/C++ 프로그래밍 : 중급] 12. 람다 표현식

by GDNGY 2023. 6. 13.

Chapter 12. 람다 표현식

람다 표현식은 C++11에서 도입된 강력한 기능입니다. 이름이 없는 함수를 직접 정의하고 이를 변수에 저장하거나 함수 인자로 전달할 수 있습니다. 이와 같은 람다 표현식의 기본 개념부터 그 활용 방법, 람다 표현식이 필요한 이유 등에 대해 알아보겠습니다.

 

반응형

 


[Chapter 12. 람다 표현식]

 

12.1. 람다 표현식의 개념

12.1.1. 람다 표현식이란?

12.1.2. 람다 표현식의 역사

12.1.3. 람다 표현식의 특징

 

12.2. 람다 표현식의 필요성과 사용 케이스

12.2.1. 람다 표현식의 필요성

12.2.2. 람다 표현식의 적용 분야

 

12.3. 람다 표현식의 기본 문법

12.3.1. 람다 표현식의 구조

12.3.2. 간단한 람다 표현식 작성법

12.3.3. 람다 표현식의 반환형 타입 추론

 

12.4. 람다 표현식과 스코프

12.4.1. 람다 표현식에서의 변수 접근

12.4.2. 캡처 절 (Capture Clause)

12.4.3. 값에 의한 캡처 (Capture-by-value)

12.4.4. 참조에 의한 캡처 (Capture-by-reference)

12.4.5. 기본 캡처 모드

 

12.5. 람다 표현식의 활용

12.5.1. 함수 인수로서의 람다

12.5.2. 반환 값으로서의 람다

12.5.3. 람다를 변수에 할당하기

12.5.4. 람다 표현식과 STL 알고리즘

12.5.5. 람다 표현식과 비동기 프로그래밍

 

12.6. 람다 표현식과 함수 객체 비교

12.6.1. 함수 객체란?

12.6.2. 람다 표현식과 함수 객체의 차이

12.6.3. 람다 표현식 vs 함수 객체 - 언제 무엇을 사용할까?

 

12.7. 람다 표현식의 고급 사용

12.7.1. 뮤테이블 람다

12.7.2. 람다 내부에서의 예외 처리

12.7.3. 람다와 템플릿

12.7.4. 재귀 람다

 

12.8. 실제 코드 예시 및 연습 문제

12.8.1. 실생활에서의 람다 표현식 활용 사례

12.8.2. 연습 문제, 풀이 및 코드 설명


12.1. 람다 표현식의 개념

C++에서 람다 표현식은 이름 없는 '익명 함수'를 만드는 강력한 방법입니다. 이는 함수를 선언하고 정의하는 전통적인 방법을 벗어나, 필요한 곳에서 직접 함수를 정의하고 사용할 수 있게 해 줍니다. 그 특성으로 인해 람다 표현식은 코드를 간결하고, 가독성 좋게 만드는 데 큰 역할을 합니다. 이 챕터에서는 람다 표현식이 무엇인지, 어떻게 사용하는지에 대해 자세히 설명합니다. C++11부터 도입된 이 기능은 현대 C++ 프로그래밍에서 핵심적인 역할을 합니다.

12.1.1. 람다 표현식이란?

람다 표현식이란, 간단히 말해 이름이 없는 함수입니다. 이름이 없다고 해서 그 기능이 떨어지는 것이 아니라, 실제로 매우 편리하고 강력한 도구로 작용합니다. C++11부터 도입된 이 기능은 코드를 간결하게 만들어주고, 일반 함수와 비교했을 때 익명성과 직관성이라는 장점을 가지고 있습니다.

 

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

 

[예제]

[캡처리스트](파라미터 리스트) -> 반환 타입 { 함수 본문 }

 

예를 들어, 두 수를 더하는 간단한 람다 표현식은 아래와 같습니다.

 

[예제]

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

 

위 코드에서 'add'는 람다 표현식을 가리키는 변수입니다. 즉, 'add'는 람다 표현식이라고 할 수 있습니다. 이렇게 람다 표현식을 변수에 할당할 수 있다는 것은 매우 유용한 특징입니다.

 

여기서 캡처리스트([])는 람다 표현식이 자신을 둘러싸는 코드의 범위(scope)에서 변수를 캡처할 수 있게 해주는 도구입니다. 캡처리스트에 아무것도 없다면 람다 표현식은 외부 변수를 캡처하지 않습니다. 람다 표현식의 캡처리스트에 대한 자세한 설명은 이후 섹션에서 다룰 예정입니다.

 

파라미터 리스트는 일반 함수의 파라미터 리스트와 동일하게 동작합니다. 위의 예제에서는 두 개의 int 타입 파라미터를 받습니다.

 

반환 타입은 '->' 기호를 이용해 지정합니다. 위의 예제에서는 int를 반환합니다. 만약 반환 타입을 명시적으로 지정하지 않으면, 컴파일러가 자동으로 반환 타입을 추론합니다.

 

함수 본문에는 실제 동작하는 코드를 넣습니다. 위의 예제에서는 두 개의 파라미터를 더해서 반환하는 코드가 있습니다.

 

람다 표현식의 강력한 특징 중 하나는 그것이 일급 객체(first-class object)라는 점입니다. 이는 람다 표현식을 변수에 할당하거나, 함수의 인수로 전달하거나, 함수의 결과로 반환할 수 있음을 의미합니다. 이런 특징은 코드를 더욱 간결하고 이해하기 쉽게 만들어줍니다.

 

함수를 정의할 위치에 제약을 받지 않기 때문에, 필요한 곳에서 즉시 사용할 수 있다는 장점도 있습니다.

 

또한, 람다 표현식은 블록 안에서 선언된 변수를 캡처할 수 있다는 특징이 있습니다. 이를 '클로저(closure)'라고도 합니다. 캡처를 사용함으로써 람다 표현식이 선언된 범위 밖의 변수에도 접근할 수 있게 됩니다.

 

다음은 람다 표현식에서 변수 캡처의 예를 보여주는 코드입니다.

 

[예제]

int a = 5;
int b = 7;

auto add = [a, b]() { return a + b; };
cout << add() << endl;  // 출력: 12

 

위 코드에서는 람다 표현식이 외부 범위의 변수 'a'와 'b'를 캡처하고 있습니다. 이를 통해 람다 표현식 내부에서도 'a'와 'b'에 접근할 수 있게 됩니다. 

 

그러나 캡처에는 주의할 점이 있습니다. 람다 표현식은 값에 의한 캡처(value capture)와 참조에 의한 캡처(reference capture)를 지원합니다. 값에 의한 캡처는 변수의 값만을 캡처하며, 참조에 의한 캡처는 변수의 참조를 캡처합니다. 참조 캡처를 사용할 때는 람다 표현식이 변수를 사용하는 동안 변수가 범위를 벗어나지 않도록 주의해야 합니다.

 

C++의 람다 표현식은 그 자체로 매우 강력한 도구이며, STL과 같은 라이브러리와 결합하면 더욱 효율적인 코드 작성이 가능해집니다. 

 

12.1.2. 람다 표현식의 역사

람다 표현식은 프로그래밍 언어의 역사에서 오래된 개념이지만, C++에는 비교적 최근인 2011년에 추가되었습니다. 이를 통해 C++는 함수를 일급 객체로 취급하는 기능을 갖게 되었고, 이는 C++의 프로그래밍 편의성과 표현력을 크게 향상했습니다.

 

람다 표현식의 뿌리는 1930년대에 거슬러 올라갑니다. 그 당시 수학자 알론조 처치는 계산 가능성의 문제를 다루기 위해 람다 대수라는 개념을 도입했습니다. 이 람다 대수는 미지의 수를 처리하는 수학의 방법을 컴퓨터가 이해할 수 있는 방식으로 표현한 것이라고 볼 수 있습니다.

 

이후, 람다 대수는 함수형 프로그래밍 언어인 Lisp의 핵심 개념 중 하나로 자리 잡았습니다. Lisp는 1950년대에 개발되었으며, 함수를 일급 객체로 취급하는 등 많은 현대 프로그래밍 언어의 핵심 개념을 도입했습니다.

 

그러나 C++가 람다 표현식을 도입하기까지는 상당한 시간이 걸렸습니다. C++는 원래 절차적 프로그래밍 언어로 시작되었고, 객체 지향 프로그래밍 기능을 추가하면서 발전했습니다. 함수를 일급 객체로 취급하는 것은 이런 절차적, 객체 지향 프로그래밍 패러다임과는 많이 다른 개념이었기 때문입니다.

 

그러나 C++11 표준에서 람다 표현식이 도입되면서, C++은 함수형 프로그래밍 패러다임의 일부를 포괄하게 되었습니다. 이는 프로그래밍의 표현력을 향상시키는 데 큰 역할을 하였고, C++ 개발자들에게 많은 혜택을 가져다주었습니다.

 

C++의 람다 표현식 도입에 영감을 준 언어 중 하나는 루비입니다. 루비는 블록이라는 개념을 통해 코드 블록을 객체로 취급할 수 있게 하여, 함수형 프로그래밍 패러다임을 도입했습니다. C++의 람다 표현식은 이 블록의 개념을 차용하였고, 이를 통해 함수를 객체로 취급할 수 있게 되었습니다.

 

결국, 람다 표현식은 C++ 프로그래밍의 유연성과 표현력을 크게 높여주는 도구가 되었습니다. 이제 이 도구를 어떻게 사용하는지에 대해 자세히 살펴보도록 하겠습니다.

 

12.1.3. 람다 표현식의 특징

람다 표현식의 특징은 여러 가지가 있습니다만, 주요 특징은 다음과 같습니다.

  • 익명 함수: 람다 표현식은 이름이 없는 익명 함수입니다. 이는 람다 표현식을 변수에 할당하거나 다른 함수에 인자로 전달할 수 있게 합니다. 이러한 특성은 함수를 재사용할 필요가 없는 경우나, 코드를 간결하게 유지하고 싶을 때 특히 유용합니다.
  • 값 캡쳐와 참조 캡쳐: 람다 표현식은 캡쳐 절을 통해 외부 변수를 캡쳐할 수 있습니다. 캡쳐할 변수를 괄호 안에 명시하면, 람다 표현식 내부에서 그 변수를 사용할 수 있게 됩니다. 값 캡쳐는 변수의 값을 람다에 복사하는 반면, 참조 캡쳐는 변수에 대한 참조를 람다에 저장합니다.
  • 유연한 반환 타입: 람다 표현식의 반환 타입은 일반적으로 컴파일러에 의해 자동으로 추론됩니다. 하지만 필요한 경우에는 개발자가 반환 타입을 명시적으로 선언할 수도 있습니다.
  • 함수와 메소드에서 사용 가능: 람다 표현식은 일반 함수나 클래스 메서드 내부에서 모두 사용할 수 있습니다. 이는 람다 표현식이 코드를 더욱 간결하고 읽기 쉽게 만드는데 도움을 줍니다.

이해를 돕기 위해 간단한 람다 표현식의 예를 들어 보겠습니다.

 

[예제]

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    
    // 람다 표현식을 사용하여 벡터 내 모든 원소를 출력합니다.
    std::for_each(numbers.begin(), numbers.end(), [](int number) {
        std::cout << number << ' ';
    });

    return 0;
}

 

위 코드에서 사용된 람다 표현식 [](int number) { std::cout << number << ' '; }는 벡터의 각 원소를 출력하는 역할을 합니다. 이 람다 표현식은 std::for_each 알고리즘의 세 번째 인자로 전달되어, 벡터의 시작부터 끝까지 각 원소에 대해 호출됩니다. 

 

람다 표현식은 복잡한 연산을 간결하게 표현할 수 있게 해주며, 코드의 가독성을 향상해 줍니다. 


12.2. 람다 표현식의 필요성과 사용 케이스

람다 표현식은 프로그래밍에서 중요한 도구입니다. 특히, 람다는 함수를 인자로 받거나 반환하는 고차 함수에서 광범위하게 사용됩니다. 또한, 복잡한 연산을 짧고 간결하게 표현하며 코드의 가독성을 향상합니다. 이외에도, 람다는 익명 함수로서, 한 번만 사용되거나 특정 스코프에서만 유용한 함수를 직접적으로 정의하는 데 효과적입니다. 이러한 이유로, 알고리즘, 이벤트 핸들러, 스레드 생성 등 다양한 분야에서 람다의 사용이 일반적입니다.

12.2.1. 람다 표현식의 필요성

C++에서 람다 표현식의 필요성은 그 효율성과 유연성에서 기인합니다. 람다 표현식은 특히 STL(Standard Template Library)에서 알고리즘을 작성할 때 편리합니다. 정렬, 검색, 변환 등의 작업을 수행하는 함수를 람다 표현식을 사용해 간단하게 작성할 수 있기 때문입니다.

 

예를 들어, 특정 벡터에서 모든 짝수를 찾아서 새로운 벡터로 복사하는 작업을 생각해 봅시다. 이를 람다 표현식 없이 구현하려면, 짝수를 판별하는 함수를 먼저 정의해야 합니다.

 

[예제]

bool isEven(int num) {
    return num % 2 == 0;
}

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> evens;

std::copy_if(v.begin(), v.end(), std::back_inserter(evens), isEven);

 

이 코드는 잘 작동하지만, isEven 함수가 오직 이 작업을 위해 만들어졌습니다. 이런 경우에 람다 표현식을 사용하면 코드를 더 간결하고 직관적으로 만들 수 있습니다. 

 

[예제]

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> evens;

std::copy_if(v.begin(), v.end(), std::back_inserter(evens), [](int num){ return num % 2 == 0; });

 

여기서 람다 표현식은 [](int num){ return num % 2 == 0; }입니다. 이것은 isEven 함수와 동일한 작업을 수행하지만, 함수를 별도로 정의할 필요 없이 바로 사용할 수 있습니다. 이처럼 람다 표현식은 코드를 더욱 간결하고 명확하게 만들어 줍니다. 

 

또한, 람다 표현식은 캡쳐 메커니즘을 제공합니다. 이를 통해 람다 표현식이 정의된 스코프의 변수를 사용할 수 있습니다. 이는 람다 표현식이 비동기 프로그래밍, 콜백 함수 등에서 유용하게 사용됩니다.

 

12.2.2. 람다 표현식의 적용 분야

람다 표현식의 편리성은 그것이 사용되는 다양한 분야에서 잘 나타납니다. 그중 일부를 아래에서 살펴보겠습니다.

 

  • STL과 함께 사용: 앞서 설명한 것처럼, STL의 알고리즘 함수들과 람다를 함께 사용하면 매우 효율적입니다. 아래의 예제는 std::for_each를 사용하여 벡터의 모든 요소를 출력하는 것을 보여줍니다. 

 

[예제]

std::vector<int> v = {1, 2, 3, 4, 5};
std::for_each(v.begin(), v.end(), [](int i){ std::cout << i << ' '; });

 

  • 비동기 프로그래밍: C++11부터 비동기 프로그래밍을 위한 표준 라이브러리가 도입되었습니다. 람다는 이러한 라이브러리와 함께 사용하여 비동기 작업을 간단하게 처리할 수 있습니다. 

 

[예제]

std::future<void> fut = std::async([]{ std::cout << "Hello from a different thread\n"; });
fut.wait();

 

  • 콜백 함수: 람다는 이름이 없는 함수이므로, 이름이 필요하지 않은 임시 작업에 매우 적합합니다. 따라서 람다는 콜백 함수로도 자주 사용됩니다. 예를 들어, GUI 프로그래밍에서 버튼 클릭 이벤트 처리기를 람다로 작성할 수 있습니다.
  • 함수 객체 생성: 람다 표현식은 함수 객체를 쉽게 생성할 수 있습니다. 이는 함수에 상태를 부여하는 데 유용하며, 함수 객체는 정렬 기준 등과 같이 사용자 정의 동작을 STL 알고리즘에 제공하는 데 사용될 수 있습니다.

 

[예제]

std::vector<int> v = {5, 3, 1, 4, 2};
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });

 

사용자 정의 컨테이너의 요소 처리: 커스텀 데이터 구조에서 요소를 처리하는 데 람다를 사용할 수 있습니다. 아래의 예제는 트리의 각 노드를 순회하고 출력하는 것을 보여줍니다.

 

[예제]

template <typename Func>
void BinaryTree::inorder_traversal(Node* node, Func visit) {
    if (node) {
        inorder_traversal(node->left, visit);
        visit(node->value);
        inorder_traversal(node->right, visit);
    }
}

BinaryTree tree;
// Populate the tree...

tree.inorder_traversal(tree.root, [](int value){ std::cout << value << ' '; });

 

이상의 예시들이 람다 표현식이 C++에서 어떻게 유용하게 사용될 수 있는지 보여줍니다. 람다의 강력함은 그것이 지역 변수에 접근하고, 익명 함수를 만들어내고, 함수의 인자로 사용할 수 있는 유연성에서 비롯됩니다.


12.3. 람다 표현식의 기본 문법

C++의 람다 표현식은 일회용 함수를 간결하게 작성할 수 있는 강력한 문법입니다. 기본 형태는 []() {}이며, 대괄호 안은 캡처 목록, 괄호 안은 매개변수 목록, 중괄호 안은 함수 본문입니다. 매개변수와 캡처는 선택적이며, 람다가 반환하는 값의 타입은 컴파일러가 추론합니다. 필요한 경우, -> 연산자를 이용해 반환 타입을 지정할 수 있습니다.

 

12.3.1. 람다 표현식의 구조

람다 표현식은 기본적으로 한 개 이상의 문장을 포함하는 익명 함수입니다.

 

람다 표현식의 구조는 다음과 같습니다:

[capture](parameters)->return-type{body}.

 

첫 번째 요소인 capture는 람다 표현식이 상위 범위의 변수를 어떻게 접근하는지를 결정합니다. 여기에는 여러 가지 옵션이 있습니다:

 

  • []: 캡처하지 않음. 즉, 람다 함수 내에서 상위 범위의 변수를 사용할 수 없습니다.
  • [=]: 모든 변수를 값으로 캡처합니다. 즉, 상위 범위의 변수의 복사본을 만들어 사용합니다.
  • [&]: 모든 변수를 참조로 캡처합니다. 이 경우 람다 함수 내에서 상위 범위의 변수를 수정할 수 있습니다.
  • [x, &y]: x는 값으로, y는 참조로 캡처합니다.

 

다음으로 parameters는 람다 함수의 매개변수 목록입니다. 이 부분은 일반 함수와 비슷하며, 필요에 따라 매개변수를 받아 처리할 수 있습니다. 

 

return-type은 람다 함수의 반환 형태를 정의합니다. 이 부분은 생략 가능하며, 생략 시 컴파일러가 body의 반환 형태를 추론하여 사용합니다. 

 

마지막으로, body는 람다 함수가 실행할 코드 블럭입니다. 이 부분에는 람다 함수가 해야 할 작업을 정의합니다.

 

간단한 예제 코드를 보겠습니다:

 

[예제]

#include <iostream>

int main() {
    int x = 0;
    auto print = [&x](){ std::cout << x << std::endl; }; // 람다 표현식

    x = 5;
    print(); // "5" 출력
}

 

위 코드에서는 x라는 변수를 참조로 캡처하는 람다 함수를 정의하고 있습니다. 이 람다 함수는 x의 값을 콘솔에 출력하는 작업을 수행합니다.

 

12.3.2. 간단한 람다 표현식 작성법

람다 표현식의 기본적인 작성법에 대해 알아보겠습니다. 람다 표현식은 일반적으로 함수 객체를 생성하므로, 함수를 필요로 하는 곳에서 람다 표현식을 사용할 수 있습니다.

 

간단한 예제를 통해 알아보죠. 두 정수를 더하는 람다 함수를 작성해 보겠습니다:

 

[예제]

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


위의 예제에서는 람다 표현식을 사용하여 두 정수를 더하는 함수를 만들고, 이를 add라는 이름의 변수에 저장했습니다. a와 b는 매개변수이며, 반환 형식은 int입니다.

 

이제 이 함수를 사용해 보겠습니다:

 

[예제]

int sum = add(1, 2);
std::cout << sum << std::endl;  // "3"을 출력합니다.


위의 코드는 add 함수를 호출하여 1과 2를 더한 결과를 sum에 저장하고 출력합니다.

 

다음은 람다 표현식을 직접 호출하는 예제입니다:

 

[예제]

([](int a, int b) -> int { return a + b; })(1, 2); // "3"을 반환합니다.

 

위의 코드는 람다 표현식을 바로 호출하여 1과 2를 더한 결과를 반환합니다. 람다 표현식 바로 뒤의 괄호 () 안에는 호출에 사용할 인수를 넣습니다.

 

람다 표현식은 간단한 한 줄짜리 함수부터 복잡한 여러 줄의 함수까지 모두 표현할 수 있습니다. 이는 특히 알고리즘을 작성할 때 유용하게 활용할 수 있습니다. 예를 들어, std::sort와 같은 알고리즘에서 비교 함수를 제공하는 데 람다 표현식을 사용할 수 있습니다.

 

[예제]

std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};
std::sort(vec.begin(), vec.end(), [](int a, int b) -> bool {
    return a < b; // 오름차순 정렬
});

 

이러한 간단한 람다 표현식 작성법을 통해 여러분의 코드를 더 간결하고 이해하기 쉽게 만들 수 있습니다. 

 

12.3.3. 람다 표현식의 반환형 타입 추론

C++에서 람다 표현식은 매우 유연한 도구입니다. 특히, 람다 표현식의 반환형 타입 추론은 코드를 깔끔하게 유지하면서 복잡한 연산을 수행할 수 있게 합니다.

 

람다 표현식의 반환형을 명시적으로 지정할 수 있지만, C++14부터는 람다 표현식의 반환형을 컴파일러가 자동으로 추론할 수 있게 되었습니다. 이는 람다 표현식이 하나의 return 문만 가지고 있는 경우에 해당하며, 반환형을 명시하지 않아도 됩니다.

 

[예제]

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

 

위의 예제에서는 람다 표현식의 반환 형식을 명시하지 않았지만, 컴파일러는 a + b의 결과가 int 형식임을 추론하므로, 이 람다 함수의 반환 형식을 int로 결정합니다. 

 

람다 표현식이 여러 return 문을 포함하고 있는 경우, 모든 return 문이 같은 형식의 값을 반환해야 합니다. 그렇지 않으면 컴파일러는 반환 형식을 추론할 수 없습니다.

 

이런 유연성은 코드를 더 간결하게 만들어주며, 개발자가 더 복잡한 논리를 구현하면서도 코드를 간결하게 유지할 수 있게 합니다. 그러나 명시적인 반환 형식이 코드의 가독성을 높일 수 있으므로, 복잡한 람다 표현식에서는 반환 형식을 명시하는 것이 좋습니다.

 

마지막으로, C++에서 람다 표현식은 반환형 뿐만 아니라 매개변수 형식까지도 추론할 수 있습니다. 이는 일반화된 람다(Generic lambda)라고 하며, C++14부터 지원합니다.

 

[예제]

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

 

위의 예제에서 generic_add 함수는 어떤 형식의 매개변수도 받을 수 있습니다. 이는 템플릿과 유사하게 작동하며, 다양한 형식에 대해 동일한 동작을 수행하는 람다 표현식을 작성할 수 있게 해 줍니다. 

 

이러한 람다 표현식의 반환형 타입 추론은 코드를 간결하고 유연하게 만들어주므로, 복잡한 로직을 구현하면서도 코드의 가독성을 유지할 수 있게 합니다. 이해가 잘 되지 않는다면, 람다 표현식을 사용하는 여러 예제를 직접 작성해 보는 것이 좋습니다.


12.4. 람다 표현식과 스코프

람다 표현식은 람다 함수를 정의하는 지점에서의 변수들을 '캡처'할 수 있는 특징을 가지고 있습니다. 이는 람다 함수가 자신이 정의된 스코프(scope)의 변수를 사용할 수 있게 해줍니다. 이를 이해하면, 람다 표현식이 어떻게 외부 변수를 접근하고, 어떻게 그 값이 유지되는지에 대해 이해하게 됩니다. 이 캡처 메커니즘은 복사(capture by copy) 또는 참조(capture by reference) 방식으로 이루어집니다.

12.4.1. 람다 표현식에서의 변수 접근

람다 표현식에서 가장 중요하고 독특한 특성 중 하나는 바로 람다가 선언된 영역의 변수에 접근할 수 있다는 것입니다. 이러한 특성은 '캡처(capture)'라고 불립니다. 람다 표현식은 복사(capture-by-copy) 또는 참조(capture-by-reference) 방식으로 외부 변수를 캡처할 수 있습니다.

 

변수를 캡처하는 방식은 람다 표현식의 시작 부분, 즉 [ ] 안에 지정합니다. 예를 들어, 람다 함수에서 외부 변수를 참조로 캡처하려면 [&]를 사용하고, 복사로 캡처하려면 [=]를 사용합니다. 또한, 특정 변수만 캡처하려는 경우 해당 변수를 명시적으로 지정할 수도 있습니다.

 

다음은 C++에서 람다 표현식을 사용하여 외부 변수를 캡처하는 예제입니다.

 

[예제]

#include <iostream>

int main() {
    int a = 5;
    int b = 10;

    // Capture 'a' by copy and 'b' by reference
    auto lambda = [a, &b]() {
        std::cout << "a: " << a << ", b: " << b << std::endl;
        // 'a' will not change outside this scope
        // 'b' will change outside this scope
        a += 1;
        b += 1;
    };

    lambda();
    // Prints: "a: 5, b: 11"

    std::cout << "a: " << a << ", b: " << b << std::endl;
    // Prints: "a: 5, b: 11"
    
    return 0;
}

 

위의 예제에서 볼 수 있듯이, 복사로 캡처된 'a' 변수는 람다 함수 내부에서 값이 변경되더라도 외부에서의 값에는 영향을 미치지 않습니다. 반면에 참조로 캡처된 'b' 변수는 람다 함수 내에서 값이 변경되면 외부에서도 그 변경이 반영됩니다. 이렇게 람다 표현식은 코드의 유연성을 높이며, 함수를 정의하고 호출하는 위치에서 필요한 데이터에 직접적으로 접근할 수 있게 합니다. 그러나 이러한 특성은 변수의 생명 주기와 스코프에 대한 이해를 필요로 합니다. 왜냐하면 캡처된 변수가 람다 표현식의 생명 주기를 벗어나면, 예기치 않은 동작이 발생할 수 있기 때문입니다. 그래서 이러한 점들은 반드시 주의해야 합니다.

 

그래서 항상 캡처할 변수의 생명주기를 고려해야 합니다. 즉, 람다 표현식이 사용될 때 해당 변수가 여전히 존재하고 유효한지 확인해야 합니다. 예를 들어, 함수가 끝나면 지역 변수는 소멸되므로, 그런 변수를 참조로 캡처하고 함수 외부에서 람다를 호출하면 문제가 발생할 수 있습니다.

 

다음은 이런 상황을 보여주는 예제입니다.

 

[예제]

#include <iostream>
#include <functional>

std::function<void()> GetLambda() {
    int a = 5;

    // Capture 'a' by reference
    auto lambda = [&a]() {
        std::cout << "a: " << a << std::endl;
    };

    return lambda;
}

int main() {
    auto lambda = GetLambda();
    lambda();  // Undefined behavior!
    
    return 0;
}

 

위 예제에서 GetLambda 함수는 람다 표현식을 반환합니다. 이 람다 표현식은 'a'라는 지역 변수를 참조로 캡처합니다. 그러나 GetLambda 함수가 반환되면서 'a'는 소멸되므로, main 함수에서 이 람다를 호출하면 정의되지 않은 동작이 발생합니다.

 

따라서, 이러한 상황을 피하기 위해 람다 표현식에서는 외부 변수를 참조로 캡처하는 것보다 복사로 캡처하는 것이 안전하다고 할 수 있습니다. 그러나 이는 추가적인 메모리를 사용하므로, 어떤 변수를 어떤 방식으로 캡처할지는 상황에 따라 달라집니다.

 

람다 표현식과 스코프에 대한 이해는 복잡한 프로그램에서 더 효과적인 코드를 작성하고, 더 나은 성능을 달성하는 데 중요합니다. 

 

12.4.2. 캡처 절 (Capture Clause)

람다 표현식에서 캡처 절은 람다 바디에서 사용할 외부 변수를 지정하는 방법을 정의합니다. 캡처 절은 대괄호([]) 안에 위치하며, 캡처 절 없이 람다 표현식을 작성하면 람다 바디에서 어떤 외부 변수도 사용할 수 없습니다. 

 

C++에서는 네 가지 유형의 캡처 절을 지원합니다.

 

  • 값에 의한 캡처(Value capture): 변수를 복사하여 람다 표현식 내부에 저장합니다.
  • 참조에 의한 캡처(Reference capture): 변수를 참조로 캡처하여 람다 표현식 내부에서 사용합니다.
  • 모든 변수를 값에 의해 캡처(Capture all by value): 모든 외부 변수를 복사하여 람다 표현식 내부에 저장합니다.
  • 모든 변수를 참조에 의해 캡처(Capture all by reference): 모든 외부 변수를 참조로 캡처하여 람다 표현식 내부에서 사용합니다.

이러한 캡처 절의 사용법은 아래 예제를 통해 살펴볼 수 있습니다.

 

[예제]

#include <iostream>

int main() {
    int x = 5;

    // Value capture
    auto valueLambda = [x]() { std::cout << "Value: " << x << std::endl; };
    valueLambda(); 

    x = 6;

    // Reference capture
    auto referenceLambda = [&x]() { std::cout << "Reference: " << x << std::endl; };
    referenceLambda();  

    int y = 7;

    // Capture all by value
    auto allValueLambda = [=]() { std::cout << "All Value: " << x << ", " << y << std::endl; };
    allValueLambda();

    // Capture all by reference
    auto allReferenceLambda = [&]() { std::cout << "All Reference: " << x << ", " << y << std::endl; };
    allReferenceLambda();  

    return 0;
}

 

위의 예제에서, valueLambda는 'x'를 값으로 캡처하고 있습니다. 즉, 람다 표현식이 생성될 때 'x'의 값이 복사되어 람다의 내부에 저장됩니다. 그래서 'x'의 값을 변경해도 람다 함수의 출력은 변하지 않습니다.

 

referenceLambda는 'x'를 참조로 캡처하므로, 'x'의 값이 변경되면 람다 함수의 출력도 함께 변경됩니다.

 

allValueLambda와 allReferenceLambda는 모든 외부 변수를 각각 값과 참조로 캡처합니다. 이들은 각각 'x'와 'y'의 값을 복사하거나 참조합니다.

 

이런 방식으로, 람다 표현식에서 캡처 절을 사용하면 람다 바디에서 어떤 외부 변수를 어떻게 사용할지 결정할 수 있습니다. 이는 람다 표현식의 유연성을 높이며, 람다를 사용하는 코드의 의도를 명확하게 표현할 수 있게 합니다.

 

12.4.3. 값에 의한 캡처 (Capture-by-value)

람다 표현식에서 "값에 의한 캡처"란 람다가 정의되는 시점에서 외부 변수의 값을 복사해 람다 내부에 저장하는 것을 말합니다. 이렇게 하면 람다가 외부 스코프의 변수를 사용할 수 있게 되며, 그 변수가 람다 이후에 변경되어도 람다 내부의 값은 그대로 유지됩니다.

 

C++에서는 값에 의한 캡처를 [변수명]이라는 형태로 표현합니다. 이를 이용해 간단한 예제를 살펴봅시다.

 

[예제]

#include <iostream>

int main() {
    int x = 5;

    // Lambda capturing 'x' by value
    auto valueLambda = [x]() { std::cout << "Value: " << x << std::endl; };
    valueLambda(); 

    x = 6; // Modifying 'x'
    valueLambda(); 

    return 0;
}


위 예제에서 valueLambda라는 람다 표현식은 'x'를 값에 의해 캡처하고 있습니다. 즉, 람다가 생성되는 시점에 'x'의 값이 복사되어 람다 내부에 저장됩니다. 따라서 'x'의 값을 변경해도 valueLambda가 출력하는 값은 변하지 않습니다.

 

이와 같이 값에 의한 캡처는 람다 표현식이 생성되는 시점의 값을 확실히 보존하고 싶을 때 유용합니다. 하지만 값에 의한 캡처는 해당 값의 복사본을 생성하므로 메모리 사용이 증가하거나, 복사 생성자가 없거나 비용이 큰 타입을 캡처할 때는 주의해야 합니다.

 

다음으로, 특정한 변수들을 캡처하거나 전체를 캡처하는 방법을 통해 람다 표현식의 유연성을 높일 수 있습니다. 이에 대해서는 '모든 변수를 값에 의해 캡처'에 대해 더 자세히 다루겠습니다.

 

람다 표현식과 값에 의한 캡처는 코드를 간결하게 만들어 주며, 복잡한 연산을 더 명확하고 읽기 쉽게 표현할 수 있게 해줍니다. 하지만 그 사용법을 정확히 이해하고 적절히 활용하는 것이 중요합니다.

 

12.4.4. 참조에 의한 캡처 (Capture-by-reference)

C++의 람다 표현식에서 "참조에 의한 캡처"는 외부 스코프의 변수를 람다 내부에서 직접 참조하는 것을 의미합니다. 이 방식을 사용하면 람다 내부에서 외부 변수의 값을 읽고 변경할 수 있습니다. 이는 "값에 의한 캡처"와 대조적으로, 참조를 통해 캡처한 변수의 값이 변경되면 람다 내부에서도 이 변경 사항을 반영하게 됩니다.

 

C++에서 참조에 의한 캡처는 [&변수명]의 형태로 표현합니다. 이를 통한 예제를 살펴보겠습니다.

 

[예제]

#include <iostream>

int main() {
    int x = 5;

    // Lambda capturing 'x' by reference
    auto refLambda = [&x]() { std::cout << "Reference: " << x << std::endl; };
    refLambda(); 

    x = 6; // Modifying 'x'
    refLambda(); 

    return 0;
}

 

위 예제에서 refLambda라는 람다 표현식은 변수 'x'를 참조에 의해 캡처하고 있습니다. 따라서 외부에서 'x'의 값이 변경되면 refLambda에서도 이 변경 사항이 반영됩니다.

 

이처럼 참조에 의한 캡처는 람다 외부의 변수를 직접 조작하거나, 크기가 큰 데이터 구조를 복사하지 않고도 사용할 수 있을 때 유용합니다. 하지만, 람다가 변수를 참조로 캡처한 경우, 람다가 존재하는 동안 해당 변수도 존재해야 하므로 생명주기를 주의해야 합니다.

 

특히 참조에 의한 캡처를 사용할 때는 람다의 생명 주기와 캡처한 변수의 생명 주기를 정확히 관리하는 것이 중요합니다. 람다의 생명 주기가 캡처한 변수의 생명 주기보다 길 경우, 람다에서는 더 이상 유효하지 않은 참조를 사용하게 될 수 있으므로 주의해야 합니다.

 

다음으로, 모든 변수를 참조에 의해 캡처하는 방법과 특정한 변수들만 참조에 의해 캡처하는 방법을 통해 람다 표현식의 유연성을 높일 수 있습니다. 이에 대해서는 '모든 변수를 참조에 의해 캡처'에 대해 더 자세히 다루겠습니다.

 

람다 표현식과 참조에 의한 캡처는 코드를 간결하게 만들어 주며, 복잡한 연산을 더 명확하고 읽기 쉽게 표현할 수 있게 해줍니다. 하지만 그 사용법을 정확히 이해하고 적절히 활용하는 것이 중요합니다.

 

12.4.5. 기본 캡처 모드

C++의 람다 표현식에서 "기본 캡처 모드"는 람다 표현식에서 필요한 외부 변수를 어떻게 캡처할지 정의하는 부분입니다. 기본 캡처 모드에는 값에 의한 캡처와 참조에 의한 캡처가 있습니다. 이는 람다 표현식에서 사용되는 외부 변수의 캡처 방식을 일괄적으로 설정하는 것을 의미합니다.

 

값에 의한 기본 캡처 모드는 [=]로 표현되며, 이 모드에서는 람다 표현식 내부에서 사용되는 모든 외부 변수가 값에 의해 캡처됩니다.

 

참조에 의한 기본 캡처 모드는 [&]로 표현되며, 이 모드에서는 람다 표현식 내부에서 사용되는 모든 외부 변수가 참조에 의해 캡처됩니다.

 

다음은 기본 캡처 모드를 사용한 예제입니다.

 

[예제]

#include <iostream>

int main() {
    int x = 5, y = 7;

    // All variables captured by value
    auto valueLambda = [=]() { std::cout << "Value: " << x << ", " << y << std::endl; };
    valueLambda();

    // All variables captured by reference
    auto refLambda = [&]() { x = 10; y = 20; std::cout << "Reference: " << x << ", " << y << std::endl; };
    refLambda();
    std::cout << "After lambda: " << x << ", " << y << std::endl;

    return 0;
}

 

위 예제에서 valueLambda는 모든 변수를 값에 의해 캡처하므로, 람다 표현식 내부에서 x와 y의 값을 읽을 수는 있지만 수정할 수는 없습니다. refLambda는 모든 변수를 참조에 의해 캡처하므로, 람다 표현식 내부에서 x와 y의 값을 수정할 수 있습니다. 따라서 refLambda가 실행된 후에는 x와 y의 값이 변경됩니다.

 

기본 캡처 모드는 간결한 코드를 작성할 수 있게 해주지만, 사용하는 모든 외부 변수를 같은 방식으로 캡처한다는 한계가 있습니다. 따라서 필요에 따라 특정 변수만 값을 변경할 수 있게 하거나, 특정 변수만 참조로 캡처하는 등 더 세밀한 제어가 필요한 경우에는 각 변수를 개별적으로 캡처해야 합니다.

 

이제 '값에 의한 캡처와 참조에 의한 캡처의 조합'에 대해서 알아보겠습니다. 람다 함수 내부에서 일부 변수는 값에 의해, 일부 변수는 참조에 의해 캡처되어야 하는 상황이 있을 수 있습니다. 이런 경우에는 기본 캡처 모드와 함께 개별 변수의 캡처 모드를 지정해 주면 됩니다. 기본 캡처 모드를 사용하면서 특정 변수를 다른 방식으로 캡처하려면, 그 변수를 캡처 절에 명시적으로 지정하면 됩니다.

 

다음은 이러한 방식을 사용한 예제입니다.

 

[예제]

#include <iostream>

int main() {
    int x = 5, y = 7;

    // x is captured by reference, y is captured by value
    auto mixedLambda = [&, x]() { y = 10; std::cout << "Mixed: " << x << ", " << y << std::endl; };
    mixedLambda();
    std::cout << "After lambda: " << x << ", " << y << std::endl;

    return 0;
}

 

이 예제에서는 x를 참조로, y를 값으로 캡처했습니다. 이렇게 하면 y의 값을 람다 표현식 내부에서 변경할 수 있지만, x의 값은 변경할 수 없습니다. 람다 표현식이 실행된 후에는 y의 값이 변경되었지만, x의 값은 그대로입니다.

 

이처럼 C++의 람다 표현식에서는 다양한 방식으로 외부 변수를 캡처할 수 있습니다. 기본 캡처 모드는 코드를 간결하게 만들어 주지만, 필요에 따라 각 변수를 개별적으로 캡처하는 것도 가능합니다. 이러한 유연성 덕분에 람다 표현식은 복잡한 프로그램에서도 효과적으로 활용할 수 있습니다.


12.5. 람다 표현식의 활용

코드의 가독성과 유연성을 향상시키는데 큰 역할을 합니다. 특히, 함수 객체를 인수로 받는 알고리즘, 이벤트 기반 프로그래밍, 비동기 코드에서 유용하게 사용됩니다. 또한, 람다는 클로저를 제공하여 상위 스코프의 변수를 캡처하고, 이를 통해 더 복잡한 연산을 수행하는 데 도움이 됩니다. 람다 표현식의 익명성과 짧은 수명은 로컬 연산에 이상적이며, 이를 통해 코드의 복잡성을 줄이고 의도를 명확하게 표현할 수 있습니다.

12.5.1. 함수 인수로서의 람다

람다 표현식을 함수의 인수로 사용하면, 코드를 보다 유연하고 가독성 있게 만들 수 있습니다. 이를 통해 코드의 복잡도를 줄이고, 로직을 직접적으로 표현할 수 있습니다. 특히, 함수를 인수로 받는 STL 알고리즘에서 이 기법은 매우 유용합니다.

 

다음은 STL 알고리즘 'std::sort'에서 람다를 사용하는 예제입니다.

 

[예제]

#include <vector>
#include <algorithm>

int main()
{
    std::vector<int> numbers = {5, 2, 9, 1, 5, 6};

    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });

    return 0;
}

 

여기서, 람다는 std::sort의 세 번째 인수로 전달되었습니다. 이 람다는 두 개의 인자를 받아 첫 번째 인자가 두 번째 인자보다 클 때 'true'를 반환합니다. 이를 통해 std::sort는 'numbers' 벡터를 내림차순으로 정렬하게 됩니다.

 

참고로 C언어는 람다 표현식을 지원하지 않으므로, 이와 비슷한 기능을 구현하려면 함수 포인터를 사용해야 합니다.

 

람다를 함수 인수로 사용하면 코드의 가독성을 향상시키고, 필요한 로직을 직접적으로 표현할 수 있어 유용합니다. 람다 표현식의 강력함을 이용하면, 복잡한 코드를 간결하고 이해하기 쉽게 만들 수 있습니다. 이러한 이유로 람다 표현식은 C++ 프로그래밍에서 중요한 도구로 간주됩니다. 이해하는 데 시간이 걸릴 수 있지만, 익숙해지면 코드를 보다 효과적으로 작성하는 데 도움이 될 것입니다.

 

12.5.2. 반환 값으로서의 람다

함수의 반환 값으로 람다 표현식을 사용하면, 함수가 다른 함수를 생성하고 반환하는 팩토리 함수를 작성할 수 있습니다. 이런 방식은 동적인 동작을 필요로 하는 경우 유용하게 사용될 수 있습니다.

 

다음은 람다를 반환하는 간단한 예시입니다:

 

[예제]

#include <iostream>

// 팩토리 함수
auto make_multiplier(int x) {
    return [x](int y) {
        return x * y;
    };
}

int main() {
    auto multiplier = make_multiplier(5); // 팩토리 함수를 호출하여 람다를 반환받습니다.
    std::cout << multiplier(10); // 출력: 50

    return 0;
}

 

위의 코드에서 make_multiplier 함수는 람다를 반환합니다. 이 람다는 캡처된 변수 x와 인수 y를 곱하는 연산을 수행합니다. 이렇게 반환된 람다는 multiplier 변수에 저장되어 다음 줄에서 호출됩니다.

 

주의할 점은, 람다를 반환할 때는 auto 키워드를 이용하여 반환형을 지정해야 합니다. 이는 람다 표현식이 만들어내는 함수 객체의 타입이 명시적으로는 알 수 없기 때문입니다.

 

또한, 반환된 람다가 캡처한 변수를 사용하는 경우, 해당 변수의 수명(lifetime)이 람다의 수명과 일치해야 합니다. 이를 위해 [x](int y)에서 처럼 값에 의한 캡처를 사용하거나, 캡처한 변수가 충분히 오래 살아있는 것을 보장해야 합니다.

 

C언어에서는 함수 포인터를 이용하여 비슷한 기능을 구현할 수 있지만, 람다가 제공하는 유연성과 표현력을 달성하는 것은 어렵습니다. 이런 이유로 람다는 코드를 간결하게 작성하고, 동적인 동작을 쉽게 구현할 수 있게 도와줍니다. 람다 표현식의 이해와 활용은 C++ 프로그래밍 실력을 향상하는 데 큰 도움이 됩니다.

 

12.5.3. 람다를 변수에 할당하기

람다 표현식의 강력한 특징 중 하나는 람다 함수를 변수에 할당할 수 있다는 점입니다. 이를 통해 동적으로 생성된 함수를 저장하고, 코드의 다른 부분에서 재사용할 수 있습니다.

 

[예제]

#include <iostream>

int main() {
    // 람다 표현식을 변수에 할당
    auto print_hello = []() {
        std::cout << "Hello, World!" << std::endl;
    };

    // 변수를 이용해 람다 함수 호출
    print_hello();

    return 0;
}

 

위의 코드에서 print_hello는 람다 함수를 참조하는 변수입니다. 람다 함수는 [](){ std::cout << "Hello, World!" << std::endl; } 이며, 이 함수는 "Hello, World!"를 출력합니다. auto 키워드를 이용하여 람다 함수의 타입을 자동으로 결정하게 했습니다. 이후 print_hello()를 호출하여 람다 함수를 실행할 수 있습니다. 

 

변수에 할당된 람다는 해당 변수의 수명이 유지되는 동안 여러 번 호출될 수 있습니다. 따라서 다양한 문맥에서 사용할 수 있는 동적인 동작을 캡슐화하는 데 유용합니다.

 

람다를 변수에 할당하는 것은 함수 포인터에 함수를 할당하는 것과 비슷합니다. 그러나 람다는 자신만의 스코프와 캡처된 변수를 가질 수 있으므로, 더 복잡한 동작을 쉽게 표현할 수 있습니다. 또한, 함수 포인터와는 달리 람다 표현식은 자신의 타입을 자동으로 결정할 수 있어 코드가 간결해집니다.

 

이러한 특성 덕분에 람다는 고차 함수(higher-order functions)를 작성하는데 매우 유용합니다. 고차 함수는 하나 이상의 함수를 인수로 받거나 함수를 결과로 반환하는 함수를 말합니다. C++의 표준 라이브러리에는 고차 함수를 많이 사용하는 알고리즘들이 많습니다. 이러한 알고리즘들을 활용하려면 람다 표현식의 이해가 필수적입니다.

 

람다 표현식은 C++의 핵심 기능 중 하나이며, 함수형 프로그래밍 스타일을 가능하게 합니다. 함수형 프로그래밍은 코드의 간결성, 모듈성, 재사용성을 향상시키므로, 람다 표현식은 C++ 프로그래밍 실력 향상에 많은 도움이 됩니다.

 

12.5.4. 람다 표현식과 STL 알고리즘

람다 표현식은 C++ 표준 템플릿 라이브러리(STL)와 함께 사용될 때 특히 강력해집니다. STL은 일련의 템플릿 기반의 클래스와 함수들로 구성된 라이브러리로, 반복자, 알고리즘, 컨테이너 등을 제공합니다. STL의 알고리즘들은 다양한 연산을 수행할 수 있는데, 그 연산들이 실행되는 정확한 방식은 종종 함수 객체나 람다 표현식에 의해 결정됩니다. 이는 STL 알고리즘을 활용한 코드를 간결하고 효율적으로 만들어주는 중요한 요소입니다.

 

예를 들어, STL의 sort 함수는 일련의 요소를 정렬하는 역할을 합니다. 이때 정렬의 기준이 되는 비교 함수를 람다 표현식으로 제공할 수 있습니다.

 

[예제]

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    // 정수 벡터 생성
    std::vector<int> numbers = {5, 2, 9, 1, 7, 4};

    // 람다 표현식을 이용해 내림차순 정렬
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });

    // 정렬 결과 출력
    for(int num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

 

위의 예제에서 sort 함수는 numbers 벡터를 내림차순으로 정렬합니다. 정렬 기준은 람다 표현식 [](int a, int b) { return a > b; }에 의해 제공됩니다. 이 람다 표현식은 두 정수를 인자로 받아, 첫 번째 정수가 두 번째 정수보다 크면 true를 반환합니다. 이런 방식으로 sort 함수는 람다 표현식의 도움을 받아 벡터를 내림차순으로 정렬합니다.

 

이처럼 STL 알고리즘과 람다 표현식은 서로를 보완하며 효과적으로 작동합니다. 람다 표현식은 동적인 동작을 간결하게 표현할 수 있으므로, STL 알고리즘의 유연성을 크게 높여줍니다. 반대로 STL 알고리즘은 람다 표현식의 활용 가능성을 확장시켜 줍니다. 이 두 기능의 결합은 C++ 코드를 더욱 강력하고 표현력이 풍부하게 만들어줍니다. 이를 통해 복잡한 연산도 간결하고 읽기 쉬운 코드로 표현할 수 있게 됩니다.

 

12.5.5. 람다 표현식과 비동기 프로그래밍

람다 표현식은 비동기 프로그래밍에서 특히 유용합니다. 비동기 프로그래밍이란 프로그램이 여러 작업을 동시에 실행하도록 설계된 프로그래밍 방식을 말합니다. 이는 많은 양의 작업을 처리하거나, 특정 작업의 완료를 기다리지 않고 다른 작업을 시작하려는 경우에 유용합니다.

 

C++에서는 std::async, std::future와 같은 기능을 제공하여 비동기 작업을 지원합니다. 람다 표현식은 이러한 비동기 작업에서 실행할 코드를 제공하는 간편하고 효과적인 방법입니다.

 

아래의 예제를 보겠습니다. 여기서는 람다 표현식을 사용해 비동기 작업을 생성하고 결과를 반환합니다.

 

[예제]

#include <future>
#include <iostream>

int main() {
    // 비동기 작업 생성
    auto future = std::async([]() {
        // 복잡한 계산 수행 (예: 1부터 100까지 더하기)
        int sum = 0;
        for(int i = 1; i <= 100; ++i) {
            sum += i;
        }

        // 계산 결과 반환
        return sum;
    });

    // 비동기 작업의 결과 얻기
    int result = future.get();

    // 결과 출력
    std::cout << "The sum is " << result << std::endl;

    return 0;
}

 

위의 예제에서 std::async 함수는 람다 표현식을 인자로 받아, 이 람다 표현식을 별도의 스레드에서 실행하는 비동기 작업을 생성합니다. 람다 표현식은 1부터 100까지의 합을 계산하고 이를 반환합니다. future.get()은 비동기 작업의 결과를 얻습니다. 이 결과는 이후에 출력됩니다. 

 

이처럼 람다 표현식은 비동기 프로그래밍에서 강력한 도구로 작용합니다. 복잡한 계산이나 시간이 오래 걸리는 작업을 별도의 스레드에서 실행하도록 하여, 메인 스레드가 다른 작업을 계속 진행할 수 있게 해줍니다. 또한 람다 표현식은 코드를 간결하게 작성할 수 있도록 도와줍니다. 이러한 특징들 덕분에 람다 표현식은 비동기 프로그래밍에서 필수적인 도구로 활용되고 있습니다. 


12.6. 람다 표현식과 함수 객체 비교

람다 표현식과 함수 객체(또는 함수자)는 비슷한 동작을 수행하지만, 람다는 보다 간결하고 직관적인 구문을 제공합니다. 함수 객체는 클래스를 정의하고 연산자 오버로딩을 통해 함수처럼 동작하는 객체를 만드는 것을 의미합니다. 람다 표현식은 익명 함수로, 필요한 곳에서 바로 정의하고 사용할 수 있습니다. 그렇기 때문에 짧은 함수를 표현할 때 람다는 코드의 가독성을 높이고 복잡성을 줄이는 데 도움이 됩니다.

12.6.1. 함수 객체란?

함수 객체(Function Object), 또는 함수자(Functor)라고 불리는 것은 특별한 종류의 객체입니다. 이는 객체지만 함수처럼 동작하며 호출할 수 있습니다. 어떻게 가능한 것일까요? 이는 C++의 연산자 오버로딩 특징 덕분입니다. 연산자 오버로딩을 사용하면, 우리는 기존 연산자에 대해 새로운 작동 방식을 정의할 수 있습니다. 함수 객체는 '()' 연산자를 오버로딩함으로써 이루어집니다. 이렇게 함으로써, 객체 이름 뒤에 '()'를 붙여서 마치 함수를 호출하는 것처럼 사용할 수 있게 됩니다.

 

간단한 함수 객체 예시를 살펴보겠습니다.

 

[예제]

#include <iostream>

class Multiply {
public:
    int operator() (int x, int y) {
        return x * y;
    }
};

int main() {
    Multiply multiply; // 함수 객체 생성

    std::cout << multiply(10, 2); // 함수처럼 사용. 출력: 20
    return 0;
}

 

위의 예시에서 Multiply라는 클래스를 정의했습니다. 이 클래스는 operator()라는 멤버 함수를 가지며, 이 함수는 두 개의 정수를 받아 곱한 결과를 반환합니다. Multiply 객체를 생성하고 '()'를 사용하여 이 멤버 함수를 호출하면, 마치 함수를 호출하는 것과 같은 효과를 얻을 수 있습니다.

 

이처럼 함수 객체는 그 자체로 상태를 가질 수 있으며, 그 상태는 함수 호출 간에 유지될 수 있습니다. 이는 람다 표현식과 비슷한 동작을 수행하지만, 람다 표현식은 보다 간결한 구문을 제공하는 장점이 있습니다.

 

12.6.2. 람다 표현식과 함수 객체의 차이

람다 표현식과 함수 객체는 비슷하게 보일 수 있지만, 그들 사이에는 몇 가지 중요한 차이점이 있습니다.

 

  • 첫째, 람다 표현식은 익명 함수를 생성하는 반면, 함수 객체는 명명된 클래스 타입을 필요로 합니다. 이러한 차이로 인해 람다는 코드를 더 간결하게 작성할 수 있습니다.
  • 둘째, 람다는 캡처 기능을 사용하여 주변의 변수를 사용할 수 있습니다. 이는 함수 객체에서는 복제 또는 참조를 통해 수동으로 수행해야 하는 작업입니다.

람다와 함수 객체의 차이를 보여주는 예시를 살펴보겠습니다.

 

[예제]

#include <iostream>

// 함수 객체
class Multiply {
public:
    int operator() (int x, int y) {
        return x * y;
    }
};

int main() {
    Multiply multiply; // 함수 객체 생성
    std::cout << multiply(10, 2); // 함수처럼 사용. 출력: 20

    // 람다 표현식
    auto lambdaMultiply = [](int x, int y) { return x * y; };
    std::cout << lambdaMultiply(10, 2); // 출력: 20

    return 0;
}

 

위의 예제에서, 람다 표현식이 함수 객체보다 간결하다는 것을 확인할 수 있습니다. 또한, 람다 표현식은 보다 직관적으로 사용할 수 있습니다.

 

세 번째 차이점은 람다 표현식이 문맥에 따라서 자동으로 리턴 타입을 추론한다는 점입니다. 반면에, 함수 객체는 개발자가 리턴 타입을 명시적으로 선언해야 합니다.

 

마지막으로, 함수 객체는 상속을 통해 확장할 수 있지만, 람다 표현식은 확장할 수 없습니다. 이는 함수 객체가 람다 표현식보다 복잡한 동작을 구현하는데 더 유용할 수 있음을 의미합니다.

 

그러나, 이런 차이점에도 불구하고 람다 표현식과 함수 객체는 코드에서 특정 작업을 캡슐화하고 재사용하는 데 매우 유용합니다. 이들 간에 선택하는 것은 주로 코딩 스타일, 개발 환경, 그리고 특정 요구사항에 따라 달라집니다.

 

물론, 이 차이점들은 람다 표현식이 항상 함수 객체보다 나은 선택이라는 것을 의미하지는 않습니다. 상황에 따라, 함수 객체가 더 적합한 선택일 수 있습니다. 예를 들어, 함수 객체는 재사용성이 뛰어나고, 필요에 따라 더 복잡한 동작을 표현할 수 있습니다. 또한, 함수 객체는 람다 표현식이 제공하지 않는 상속 기능을 제공하여 확장성을 제공합니다.

 

함수 객체의 예시를 살펴봅시다.

 

[예제]

#include <iostream>

// 함수 객체
class Add {
public:
    int operator() (int x, int y) {
        return x + y;
    }
};

int main() {
    Add add; // 함수 객체 생성
    std::cout << add(10, 2); // 함수처럼 사용. 출력: 12

    return 0;
}

 

이 코드에서 'Add' 클래스는 '()' 연산자를 오버로드하여 함수처럼 동작하게 합니다. 이렇게 작성된 함수 객체는 람다 표현식과 유사하게 동작하지만, 클래스에 의해 정의되기 때문에 더욱 복잡한 로직을 표현할 수 있습니다.

 

이러한 이유로, 람다 표현식과 함수 객체 사이의 선택은 특정 상황과 요구사항에 따라 달라집니다. 코드의 간결성과 직관성이 중요하다면 람다 표현식이 좋은 선택일 수 있습니다. 반면, 코드의 재사용성과 확장성이 중요하다면 함수 객체가 더 나을 수 있습니다.

 

이렇게 각각의 특징을 이해하고 적절히 활용하면, 더 효율적이고 유연한 코드를 작성할 수 있습니다. 그러니 꼭 여러분의 상황과 필요에 맞게 람다 표현식과 함수 객체를 활용해 보시기 바랍니다.

 

12.6.3. 람다 표현식 vs 함수 객체 - 언제 무엇을 사용할까?

우리는 람다 표현식과 함수 객체가 어떻게 다른지 이해해야 합니다. 둘 다 함수와 같이 동작하지만, 각각의 장단점이 있습니다.

 

람다 표현식은 C++11부터 도입된 기능으로, 짧고 간결하게 코드를 작성할 수 있습니다. 이름 없는 함수인 람다 표현식은 일회성 작업에 적합하며, 함수의 인자로 전달하거나 반환값으로 사용할 수 있어 유연성이 뛰어납니다. 하지만 복잡한 로직을 표현하기에는 한계가 있습니다.

 

[예제]

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 람다 표현식 사용: 벡터의 모든 요소를 출력
    std::for_each(numbers.begin(), numbers.end(), [](int num) {
        std::cout << num << std::endl;
    });

    return 0;
}


함수 객체는 일반적인 함수와 다르게 상태를 가질 수 있습니다. 이는 클래스의 인스턴스 변수로 상태를 유지할 수 있다는 것을 의미하며, 이런 특성은 람다 표현식에는 없습니다. 또한 함수 객체는 재사용성이 좋습니다.

 

[예제]

#include <iostream>

// 함수 객체
class Add {
public:
    Add(int value) : value(value) {}
    int operator() (int x) {
        return x + value;
    }
private:
    int value;
};

int main() {
    Add add_five(5);
    std::cout << add_five(10); // 출력: 15

    return 0;
}

 

이처럼 람다 표현식과 함수 객체는 서로 다른 용도로 사용됩니다.

 

짧고 일회성인 작업은 람다 표현식을 이용해 간결하게 표현할 수 있습니다. 복잡한 로직이 필요하거나 재사용이 필요한 경우에는 함수 객체를 사용할 수 있습니다. 어떤 기능을 사용할지 선택하는 것은 개발자의 몫입니다. 람다 표현식과 함수 객체, 둘 다 자신만의 장점을 가지고 있으므로 상황에 따라 적절히 선택하여 사용하면 됩니다. 핵심은 코드가 읽기 쉽고, 유지 보수하기 쉬워야 한다는 것을 항상 기억하시기 바랍니다.


12.7. 람다 표현식의 고급 사용

C++14 이후로 람다 표현식에 auto 키워드를 사용할 수 있게 되었는데, 이를 통해 제네릭 람다를 작성할 수 있습니다. 또한 람다 캡처에 대해 더 깊게 배울 수 있습니다. 예를 들어, [=]는 모든 변수를 값으로 캡처하고, [&]는 모든 변수를 참조로 캡처합니다. 특정 변수만 캡처하려면, [x, &y]와 같이 작성할 수 있습니다.

12.7.1. 뮤테이블 람다

뮤테이블 람다는 캡처한 값을 람다 함수 내에서 변경할 수 있도록 합니다.

 

기본적으로 람다 표현식에서 캡처한 값을 변경하는 것은 허용되지 않습니다. 예를 들어 다음과 같은 코드를 작성하면 컴파일 에러가 발생합니다:

 

[예제]

int main()
{
    int value = 0;
    auto lambda = [value](){ value += 1; }; // 컴파일 에러!
    lambda();
    return 0;
}
위 코드는 value를 값으로 캡처했지만 람다 함수 내부에서 value를 변경하려 했기 때문에 컴파일 에러가 발생합니다. 이러한 제약을 해결하려면 mutable 키워드를 사용해야 합니다.

[예제]
int main()
{
    int value = 0;
    auto lambda = [value]() mutable { value += 1; };
    lambda();
    return 0;
}

 

위 코드는 뮤테이블 람다를 사용하여 캡처한 value를 람다 함수 내에서 변경하였습니다. 뮤테이블 람다를 사용하면 값 캡처를 통해 람다 함수 외부의 영향을 받지 않으면서도 람다 함수 내에서 값 변경이 가능해집니다.

 

하지만, 여기서 주의할 점이 있습니다. mutable 키워드는 람다 함수 내부에서만 캡처한 값을 변경할 수 있게 해줍니다. 즉, 람다 함수 내부에서 캡처한 값을 변경해도 람다 함수 외부의 실제 값에는 영향을 미치지 않습니다. 위의 예제에서 뮤테이블 람다를 호출한 후에 value를 출력하면 그 값은 여전히 0입니다. 따라서 mutable 키워드를 사용할 때는 이 점을 주의해야 합니다. 뮤테이블 람다를 사용할 때는 람다 함수의 순수성(purity)을 포기하게 되므로, 사이드 이펙트를 주의해야 합니다.

 

12.7.2. 람다 내부에서의 예외 처리

C++에서 예외 처리는 try, catch, throw 세 가지 키워드를 사용합니다. 람다 함수 내에서도 동일하게 예외를 처리할 수 있습니다.

 

예를 들어, 다음과 같이 람다 표현식 내에서 예외를 발생시키고 처리하는 예제 코드를 살펴보겠습니다:

 

[예제]

auto divide = [](double a, double b) {
    if (b == 0.0) {
        throw std::invalid_argument("Divisor cannot be zero");
    }
    return a / b;
};

try {
    double result = divide(5.0, 0.0);
} catch (const std::invalid_argument& e) {
    std::cerr << e.what() << std::endl;
}

 

이 예제에서는 람다 함수 divide에서 분모가 0일 경우 예외를 던집니다. 이를 try 블록 안에서 호출하고, 만약 예외가 발생한다면 catch 블록에서 처리합니다.

 

이처럼 람다 함수 내에서도 예외를 던지고 처리할 수 있습니다. 하지만 이렇게 예외 처리 코드가 포함된 람다 표현식은 가독성이 떨어질 수 있으므로, 람다 표현식은 가능한 한 간단하게 유지하는 것이 좋습니다. 복잡한 로직이나 예외 처리가 필요한 경우에는 일반 함수나 클래스를 사용하는 것이 더 적합할 수 있습니다.

 

람다 함수 내에서의 예외 처리는 람다 함수를 더욱 강력하게 만들어 줍니다. 그러나 이를 사용할 때에는 예외 처리 코드로 인해 람다 함수가 복잡해지는 것을 주의해야 합니다. 람다 함수의 주요한 장점 중 하나는 간결함에 있기 때문입니다. 따라서 람다 함수를 작성할 때에는 간결함을 유지하면서도 예외 처리 등 필요한 로직을 잘 구성하는 것이 중요합니다.

 

이러한 점을 고려할 때, 아래와 같은 람다 표현식의 예제는 좀 더 복잡한 시나리오를 처리할 수 있도록 설계되었습니다:

 

[예제]

auto process_data = [](std::vector<int>& data) {
    try {
        for (int i : data) {
            if (i < 0) {
                throw std::runtime_error("Negative value detected");
            }
            // 데이터 처리 로직...
        }
    } catch (const std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        // 필요한 경우 여기서 추가 처리를 수행합니다.
    }
};

 

이 예제에서는 process_data 람다 함수가 벡터의 모든 요소를 처리하려고 시도합니다. 만약 음수 값이 발견되면 런타임 예외를 발생시킵니다. 이 예외는 람다 함수 내의 catch 블록에서 캐치되어 처리됩니다. 이렇게 람다 표현식 내에서 예외 처리를 수행하면 호출자가 예외를 직접 처리할 필요 없이, 람다 함수 내에서 문제를 해결할 수 있습니다.

 

람다 표현식을 이용하면 이와 같이 작지만 강력한 함수를 쉽게 만들 수 있습니다. 그러나 람다 함수가 너무 복잡해지는 것은 피해야 합니다. 필요 이상으로 복잡한 람다는 코드의 가독성을 해칠 수 있습니다. 따라서 람다 함수는 작고, 간결하게 유지하는 것이 가장 좋습니다.

 

이렇게 람다 함수 내에서의 예외 처리에 대해 배워보았습니다. 이를 활용하면 람다 함수를 더욱 효과적으로 사용할 수 있을 것입니다. 하지만 기억해야 할 것은 예외 처리를 필요 이상으로 복잡하게 만들지 않는 것입니다. 람다 표현식의 가장 큰 장점은 그 간결함에 있습니다. 따라서 복잡한 예외 처리가 필요한 경우, 람다 대신 일반 함수를 사용하는 것을 고려해 보세요.

 

12.7.3. 람다와 템플릿

C++의 템플릿 기능은 재사용 가능한 코드를 작성하는 데 있어 매우 유용한 도구입니다. 람다 표현식을 템플릿과 결합하면, 타입에 대해 중립적인 범용 코드를 작성할 수 있습니다.

 

다음 예제는 템플릿과 람다를 함께 사용하여, 벡터의 모든 원소를 출력하는 범용 함수를 만드는 방법을 보여줍니다:

 

[예제]

template<typename T>
void print_vector(const std::vector<T>& vec) {
    std::for_each(vec.begin(), vec.end(), [](const T& val) {
        std::cout << val << " ";
    });
    std::cout << std::endl;
}

// 사용 예
std::vector<int> int_vec = {1, 2, 3, 4, 5};
print_vector(int_vec);

std::vector<std::string> str_vec = {"Hello", "World", "from", "C++"};
print_vector(str_vec);

 

이 코드는 print_vector라는 템플릿 함수를 정의합니다. 이 함수는 벡터를 인자로 받아, 벡터의 모든 원소를 출력합니다. 벡터의 원소 타입은 아무거나 될 수 있기 때문에, T라는 템플릿 타입 매개변수를 사용합니다. 이 템플릿 함수 내에서 람다 표현식을 사용하여 벡터의 각 원소를 출력합니다. 이 람다 표현식에서도 T 타입을 사용하므로, 벡터의 원소 타입에 상관없이 함수가 동작합니다. 

 

템플릿과 람다를 함께 사용하면, 코드의 재사용성을 높이고 유지 관리를 용이하게 할 수 있습니다. 특히, 템플릿 매개변수로 람다 표현식을 받는 함수를 만들면, 함수의 동작을 사용자가 정의할 수 있게 되어 유연성이 크게 향상됩니다.

 

하지만 이런 특성은 복잡성을 증가시킬 수도 있습니다. 람다와 템플릿은 각각 복잡한 주제이므로, 함께 사용할 때는 주의가 필요합니다. 특히, 오류 메시지를 이해하거나 디버깅하는 데 어려움을 겪을 수 있습니다. 따라서 이런 기능을 사용할 때는 필요한 경우에만 사용하고, 가능한 한 간결하게 코드를 작성하는 것이 중요합니다.

 

12.7.4. 재귀 람다

"재귀 람다"는 람다 표현식이 자기 자신을 호출하는 것을 의미합니다. 이는 많은 알고리즘, 특히 분할 정복 알고리즘에서 유용하게 사용됩니다. 하지만 람다 표현식은 선언되기 전에는 이름이 없으므로, 자신을 직접 참조할 수 없습니다. 이 문제를 해결하기 위해, 람다를 자신이 포함된 변수에 할당하고, 그 변수를 람다의 캡처 목록에 포함시킵니다. 그럼 이 변수를 통해 람다가 자신을 참조할 수 있게 됩니다.

 

예를 들어, 재귀적으로 팩토리얼을 계산하는 람다를 만들어 보겠습니다:

 

[예제]

#include <iostream>
#include <functional>

int main() {
    std::function<int(int)> factorial = [&factorial](int i) {
        return i > 1 ? i * factorial(i - 1) : 1;
    };

    std::cout << "5! = " << factorial(5) << std::endl;

    return 0;
}

 

이 예제에서 std::function을 사용해 함수 객체를 정의하고, 람다를 그 객체에 할당했습니다. 그런 다음 람다의 캡처 목록에 factorial을 포함시켜, 람다가 자신을 호출할 수 있도록 했습니다. 그 결과, 이 람다는 재귀적으로 팩토리얼을 계산할 수 있게 됩니다.

 

재귀 람다를 사용하면 코드를 간결하게 만들 수 있지만, 디버깅이 어렵고 스택 오버플로우 같은 문제를 일으킬 수 있으므로 주의해야 합니다. 특히 람다의 캡처 목록에 대한 이해가 필요합니다. 그렇지 않으면, 예상치 못한 사이드 이펙트를 일으킬 수 있습니다.

 

재귀 람다는 그 자체로 강력한 도구지만, std::function과 같은 다른 라이브러리 기능과 함께 사용하면 더욱 강력해집니다. 이를 통해 람다 표현식의 편리성과 유연성을 극대화할 수 있습니다. 하지만, 이러한 고급 기능을 사용하려면 C++의 람다와 캡처 메커니즘에 대한 깊은 이해가 필요합니다. 따라서 이러한 기능을 사용하기 전에 이러한 주제에 대해 충분히 연습하고 이해하는 것이 중요합니다.

 

2.8.1. 실생활에서의 람다 표현식 활용 사례

람다 표현식은 실제 코드 작성에서 광범위하게 사용되고 있습니다. 아래에 그 중 몇 가지 예를 들어 보겠습니다.

 

  • 컨테이너 정렬: C++ STL의 sort 함수는 세 번째 인자로 비교 함수를 받을 수 있습니다. 이때 람다를 이용하면 간편하게 정렬 기준을 제공할 수 있습니다.

 

[예제]

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};
    
    std::sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b;   // 내림차순 정렬
    });

    return 0;
}

 

  • 백그라운드 작업: 병렬 프로그래밍 라이브러리나 스레드 라이브러리에서는 종종 백그라운드에서 실행할 작업을 함수 객체로 전달받습니다. 람다를 사용하면 이를 간결하게 표현할 수 있습니다.

 

[예제]

#include <thread>

int main() {
    std::thread t([]() {
        // 백그라운드에서 실행할 코드
    });

    t.join();

    return 0;
}

 

이렇게 람다 표현식은 코드의 간결성과 가독성을 높여줍니다. 물론, 복잡한 로직이나 재사용이 필요한 경우에는 별도의 함수를 정의하는 것이 좋습니다. 람다는 '지역적으로 한 번만 사용되는 함수'에 가장 적합합니다.

 

12.8.2. 연습 문제, 풀이 및 코드 설명

이제 본격적으로 람다 표현식을 이용한 연습 문제를 살펴봅시다. 이 연습문제는 정수의 벡터를 입력받아, 특정 조건을 만족하는 원소를 찾아 출력하는 코드를 작성하는 것입니다.

 

문제: 벡터의 원소 중 홀수인 원소만 출력하는 프로그램을 작성해보세요.

이 문제를 풀기 위해 std::for_each 알고리즘과 람다 표현식을 사용해 보겠습니다. std::for_each는 범위에 속한 모든 원소에 대해 함수를 호출하는 알고리즘입니다. 이 함수로 람다 표현식을 전달하면 각 원소에 대해 람다가 실행되는 것입니다.

[예제]

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    std::for_each(nums.begin(), nums.end(), [](int n) {
        if (n % 2 != 0) {
            std::cout << n << " ";
        }
    });

    return 0;
}

 

위의 코드는 nums 벡터의 모든 원소에 대해 람다 표현식을 실행합니다. 람다 표현식은 주어진 정수 n이 홀수인지를 확인하고, 홀수라면 그 값을 출력합니다.

 

for_each와 람다 표현식의 조합은 코드를 깔끔하게 만들어주며, 의도를 명확하게 전달해줍니다. 이러한 스타일은 함수형 프로그래밍에서 자주 볼 수 있는 패턴입니다.

 

위 코드의 출력 결과는 다음과 같습니다.

 

1 3 5 7 9

 

이 문제를 통해 우리는 람다 표현식을 사용하여 코드의 가독성을 높이고, 코드의 복잡성을 줄일 수 있음을 알 수 있습니다. 

 

문제: 벡터의 원소 중 5보다 큰 원소만 출력하는 프로그램을 작성해보세요.

이 문제 역시 std::for_each 알고리즘과 람다 표현식을 이용하여 풀 수 있습니다. 람다 표현식의 조건을 변경하여 원소가 5보다 큰지를 검사하면 됩니다.

 

[예제]

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    std::for_each(nums.begin(), nums.end(), [](int n) {
        if (n > 5) {
            std::cout << n << " ";
        }
    });

    return 0;
}

 

이 코드는 nums 벡터의 모든 원소에 대해 람다 표현식을 실행합니다. 람다 표현식은 주어진 정수 n이 5보다 큰지 확인하고, 그렇다면 그 값을 출력합니다.

 

위 코드의 출력 결과는 다음과 같습니다.

 

6 7 8 9

 

이번 문제 역시 람다 표현식을 통해 간단하게 해결할 수 있습니다. 람다 표현식은 코드의 가독성을 향상하고, 복잡성을 줄이는 데 매우 효과적입니다. 다음 챕터에서도 더 복잡한 문제에 람다 표현식을 활용해 봅시다. 이를 통해 람다 표현식의 유용성과 효율성을 직접 체험할 수 있을 것입니다.

 

문제: 문자열을 원소로 갖는 벡터가 주어졌을 때, 각 문자열이 "apple"인지 확인하는 프로그램을 작성해 보세요.

문자열을 처리하는 람다 표현식을 작성하는 것도 가능합니다. 문자열 비교를 위해 std::string의 compare 함수를 사용할 수 있습니다.

 

[예제]

#include <algorithm>
#include <vector>
#include <iostream>
#include <string>

int main() {
    std::vector<std::string> fruits = {"apple", "banana", "cherry", "apple", "orange"};

    std::for_each(fruits.begin(), fruits.end(), [](std::string s) {
        if (!s.compare("apple")) {
            std::cout << s << " ";
        }
    });

    return 0;
}

 

이 코드는 fruits 벡터의 모든 원소에 대해 람다 표현식을 실행합니다. 람다 표현식은 주어진 문자열 s가 "apple"인지를 확인하고, 그렇다면 그 값을 출력합니다.

 

위 코드의 출력 결과는 다음과 같습니다.

 

apple apple

 

람다 표현식은 간결한 문법으로 다양한 타입의 데이터를 처리하는 데 사용할 수 있습니다. 또한, 필요에 따라 복잡한 로직을 포함하는 함수를 빠르고 효율적으로 작성하는 데 도움이 됩니다. 이를 통해, C++ 프로그래밍에서 더 효율적이고 생산적인 코드를 작성하는 데 도움이 됩니다.

 

 

 

2023.06.12 - [GD's IT Lectures : 기초부터 시리즈/C, C++ 기초부터 ~] - [C/C++ 프로그래밍 : 중급] 11. 스마트 포인터

 

[C/C++ 프로그래밍 : 중급] 11. 스마트 포인터

Chapter 11. 스마트 포인터 스마트 포인터는 C++에서 동적 메모리 관리를 단순화하는 도구입니다. 그들은 기본 포인터와 비슷하게 동작하지만, 적절한 시점에 자동으로 메모리를 해제하여 메모리

gdngy.tistory.com

 

반응형

댓글