C++ 람다 식

C++11 이상에서는 람다 식(종종 람다라고도 함)은 함수에 인수로 호출되거나 전달되는 위치에서 익명 함수 개체(클로저)를 정의하는 편리한 방법입니다. 일반적으로 람다는 알고리즘 또는 비동기 함수에 전달되는 몇 줄의 코드를 캡슐화하는 데 사용됩니다. 이 문서에서는 람다를 정의하고 다른 프로그래밍 기술과 비교합니다. 해당 장점을 설명하고 몇 가지 기본 예제를 제공합니다.

람다 식의 일부

다음은 함수에 세 번째 인수 std::sort() 로 전달되는 간단한 람다입니다.

#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        // Lambda expression begins
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
        } // end of lambda expression
    );
}

이 그림에서는 람다 구문의 일부를 보여 줍니다.

Diagram that identifies the various parts of a lambda expression.

람다 식 예제는 [=]() 변경 가능한 throw() -> int { return x+y; } [=]는 캡처 절입니다. C++ 사양의 람다 소개자라고도 함 매개 변수 목록에 대한 괄호입니다. 변경 가능한 키워드(keyword) 선택 사항입니다. throw()는 선택적 예외 사양입니다. -> int는 선택적 후행 반환 형식입니다. 람다 본문은 중괄호 안의 문으로 구성되거나 x+y를 반환합니다. 이러한 내용은 이미지 다음에 자세히 설명되어 있습니다.

  1. capture 절 (C++ 사양의 람다 소개자 라고도 함)

  2. 매개 변수 목록 선택 사항입니다. (람다 선언자라고도 함)

  3. 변경 가능한 사양 선택 사항입니다.

  4. exception-specification 선택 사항입니다.

  5. 후행 반환 형식 선택 사항입니다.

  6. 람다 본문입니다.

Capture 절

람다는 본문에 새 변수( C++14)를 도입할 수 있으며, 주변 범위에서 변수에 액세스하거나 캡처할 수도 있습니다. 람다는 캡처 절로 시작합니다. 캡처되는 변수와 캡처가 값 또는 참조에 의한 것인지를 지정합니다. 앰퍼샌드(&) 접두사를 가진 변수는 참조 및 값으로 액세스하지 않은 변수에 의해 액세스됩니다.

빈 캡처 절인 [ ]는 람다 식의 본문이 바깥쪽 범위의 변수에 액세스하지 않음을 나타냅니다.

캡처 기본 모드를 사용하여 람다 본문 [&] 에서 참조하는 외부 변수를 캡처하는 방법을 나타낼 수 있습니다. 참조하는 모든 변수는 참조 [=] 로 캡처되며 값으로 캡처됨을 의미합니다. 기본 캡처 모드를 사용하고 특정 변수에 대해서는 반대 모드를 지정할 수 있습니다. 예를 들어, 람다 본문이 외부 변수 total에 참조별로 액세스하고 외부 변수 factor에 값별로 액세스하는 경우 다음 캡처 절이 동일합니다.

[&total, factor]
[factor, &total]
[&, factor]
[=, &total]

캡처 기본값을 사용하는 경우 람다 본문에 멘션 변수만 캡처됩니다.

캡처 절에 캡처 기본값 &이 포함되어 있으면 해당 캡처 절의 캡처에 해당 형식 &identifier을 가질 수 있는 식별자가 없습니다. 마찬가지로 캡처 절에 캡처 기본값 =이 포함된 경우 해당 캡처 절의 캡처에는 해당 형식 =identifier이 있을 수 없습니다. 식별자이거나 this 캡처 절에 두 번 이상 나타날 수 없습니다. 다음 코드 조각은 몇 가지 예를 보여 줍니다.

struct S { void f(int i); };

void S::f(int i) {
    [&, i]{};      // OK
    [&, &i]{};     // ERROR: i preceded by & when & is the default
    [=, this]{};   // ERROR: this when = is the default
    [=, *this]{ }; // OK: captures this by value. See below.
    [i, i]{};      // ERROR: i repeated
}

다음 variadic 템플릿 예제와 같이 줄임표가 뒤에 오는 캡처는 팩 확장입니다.

template<class... Args>
void f(Args... args) {
    auto x = [args...] { return g(args...); };
    x();
}

클래스 멤버 함수의 본문에서 람다 식을 사용하려면 캡처 절에 포인터를 전달 this 하여 바깥쪽 클래스의 멤버 함수 및 데이터 멤버에 대한 액세스를 제공합니다.

Visual Studio 2017 버전 15.3 이상 (모드 이상에서 /std:c++17 사용 가능): this 캡처 절에 지정하여 *this 포인터를 값으로 캡처할 수 있습니다. 값으로 캡처하면 람다가 호출되는 모든 호출 사이트에 전체 닫기를 복사합니다. (클로저는 람다 식을 캡슐화하는 무명 함수 개체입니다.) 값별 캡처는 람다가 병렬 또는 비동기 작업에서 실행되는 경우에 유용합니다. NUMA와 같은 특정 하드웨어 아키텍처에서 특히 유용합니다.

클래스 멤버 함수와 함께 람다 식을 사용하는 방법을 보여 주는 예제는 람다 식 예제에서 "예제: 메서드에서 람다 식 사용"을 참조하세요.

캡처 절을 사용하는 경우 특히 다중 스레딩과 함께 람다를 사용하는 경우 이러한 점을 염두에 두는 것이 좋습니다.

  • 참조 캡처를 사용하여 외부 변수를 수정할 수 있지만 값 캡처는 수정할 수 없습니다. (mutable 복사본을 수정할 수 있지만 원본은 수정할 수 없습니다.)

  • 참조 캡처는 외부 변수에 대한 업데이트를 반영하지만 값 캡처는 반영되지 않습니다.

  • 참조 캡처는 수명 종속성을 발생시키지만 값 캡처에는 수명 종속성이 없습니다. 람다가 비동기적으로 실행되는 경우 특히 중요합니다. 비동기 람다에서 참조로 로컬을 캡처하는 경우 람다가 실행될 때까지 해당 로컬이 쉽게 사라질 수 있습니다. 코드로 인해 런타임에 액세스 위반이 발생할 수 있습니다.

일반화된 캡처(C++14)

C++14에서는 람다 함수의 바깥쪽 범위에 해당 변수가 존재하지 않아도 캡처 절에 새 변수를 도입하고 초기화할 수 있습니다. 이러한 초기화는 임의의 식으로 표현할 수 있습니다. 새 변수의 형식은 식에서 생성되는 형식에서 추론됩니다. 이 기능을 사용하면 주변 범위에서 이동 전용 변수(예: std::unique_ptr)를 캡처하고 람다에서 사용할 수 있습니다.

pNums = make_unique<vector<int>>(nums);
//...
      auto a = [ptr = move(pNums)]()
        {
           // use ptr
        };

매개 변수 목록

람다는 변수를 캡처하고 입력 매개 변수를 수락할 수 있습니다. 매개 변수 목록(표준 구문의 람다 선언자 )은 선택 사항이며 대부분의 측면에서 함수의 매개 변수 목록과 유사합니다.

auto y = [] (int first, int second)
{
    return first + second;
};

C++14에서 매개 변수 형식이 제네릭인 경우 키워드(keyword) 형식 지정자로 사용할 auto 수 있습니다. 이 키워드(keyword) 컴파일러에 함수 호출 연산자를 템플릿으로 만들도록 지시합니다. 매개 변수 목록의 auto 각 인스턴스는 고유 형식 매개 변수와 동일합니다.

auto y = [] (auto first, auto second)
{
    return first + second;
};

람다 식은 다른 람다 식을 인수로 사용할 수 있습니다. 자세한 내용은 람다 식 예제 문서의 "상위 순서 람다 식"을 참조하세요.

매개 변수 목록은 선택 사항이므로 람다 식에 인수를 전달하지 않고 람다 선언자에 예외 사양, 후행 반환 형식 또는 mutable이 매개 변수가 포함되지 않은 경우 빈 괄호를 생략할 수 있습니다.

변경 가능한 사양

일반적으로 람다의 함수 호출 연산자는 const-by-value이지만 키워드(keyword) 사용하면 mutable 취소됩니다. 변경 가능한 데이터 멤버를 생성하지 않습니다. 사양을 mutable 사용하면 람다 식의 본문이 값으로 캡처되는 변수를 수정할 수 있습니다. 이 문서의 뒷부분에 있는 몇 가지 예제에서는 사용 mutable방법을 보여 줍니다.

예외 사양

예외 사양을 noexcept 사용하여 람다 식이 예외를 throw하지 않음을 나타낼 수 있습니다. 일반 함수와 마찬가지로 Microsoft C++ 컴파일러는 람다 식이 예외 사양을 선언 noexcept 하고 람다 본문이 예외를 throw하는 경우 다음과 같이 경고 C4297을 생성합니다.

// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
   []() noexcept { throw 5; }();
}

자세한 내용은 예외 사양(throw)을 참조하세요.

반환 형식

람다 식의 반환 형식은 자동으로 추론됩니다. 후행 반환 형식auto 지정하지 않는 한 키워드(keyword) 사용할 필요가 없습니다. 후행 반환 형식일반 함수 또는 멤버 함수의 반환 형식 부분과 유사합니다. 그러나 반환 형식은 매개 변수 목록 뒤에 와야 하며 반환 형식 앞에 trailing-return-type 키워드 ->를 포함해야 합니다.

람다 본문에 return 문이 하나만 포함된 경우 람다 식의 반환 형식 부분을 생략할 수 있습니다. 또는 식이 값을 반환하지 않는 경우 람다 본문에 단일 return 문이 포함되어 있으면 컴파일러는 반환 식의 형식에서 반환 형식을 추론합니다. 그렇지 않으면 컴파일러는 반환 형식을 .로 void추론합니다. 이 원칙을 설명하는 다음 예제 코드 조각을 고려합니다.

auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; };  // ERROR: return type is void, deducing
                                  // return type from braced-init-list isn't valid

람다 식은 다른 람다 식을 반환 값으로 생성할 수 있습니다. 자세한 내용은 람다 식 예제의 "상위 순서 람다 식"을 참조하세요.

람다 본문

람다 식의 람다 본문은 복합 문입니다. 일반 함수 또는 멤버 함수의 본문에 허용되는 모든 항목을 포함할 수 있습니다. 일반 함수와 람다 식 모두의 본문은 다음과 같은 종류의 변수에 액세스할 수 있습니다.

  • 앞의 설명대로 바깥쪽 범위에서 캡처된 변수

  • 매개 변수.

  • 로컬로 선언된 변수입니다.

  • 클래스 내에서 선언되고 캡처되는 경우 클래스 this 데이터 멤버입니다.

  • 정적 스토리지 기간이 있는 모든 변수(예: 전역 변수).

다음 예제에는 n 변수를 값별로 명시적으로 캡처하고 m 변수를 참조별로 암시적으로 캡처하는 람다 식이 포함되어 있습니다.

// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;

int main()
{
   int m = 0;
   int n = 0;
   [&, n] (int a) mutable { m = ++n + a; }(4);
   cout << m << endl << n << endl;
}
5
0

n 변수는 값별로 캡처되므로 람다 식을 호출한 후 해당 값이 0으로 유지됩니다. 사양은 mutablen 람다 내에서 수정할 수 있습니다.

람다 식은 자동 스토리지 기간이 있는 변수만 캡처할 수 있습니다. 그러나 람다 식의 본문에 정적 스토리지 기간이 있는 변수를 사용할 수 있습니다. 다음 예제는 generate 함수 및 람다 식을 사용하여 vector 개체의 각 요소에 값을 할당합니다. 람다 식은 정적 변수를 수정하여 다음 요소의 값을 생성합니다.

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this isn't thread-safe and is shown for illustration only
}

자세한 내용은 생성을 참조하세요.

다음 코드 예제에서는 이전 예제의 함수를 사용하고 C++ 표준 라이브러리 알고리즘 generate_n을 사용하는 람다 식의 예를 추가합니다. 이 람다 식은 vector 개체의 요소를 이전의 두 요소의 합에 할당합니다. mutable 키워드(keyword) 람다 식의 본문이 외부 변수 x 의 복사본과 y람다 식이 값으로 캡처하는 복사본을 수정할 수 있도록 사용됩니다. 람다 식이 원래 변수 xy를 값별로 캡처하기 때문에 람다가 실행된 후에도 값이 1로 유지됩니다.

// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename C> void print(const string& s, const C& c) {
    cout << s;

    for (const auto& e : c) {
        cout << e << " ";
    }

    cout << endl;
}

void fillVector(vector<int>& v)
{
    // A local static variable.
    static int nextValue = 1;

    // The lambda expression that appears in the following call to
    // the generate function modifies and uses the local static
    // variable nextValue.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    //WARNING: this isn't thread-safe and is shown for illustration only
}

int main()
{
    // The number of elements in the vector.
    const int elementCount = 9;

    // Create a vector object with each element set to 1.
    vector<int> v(elementCount, 1);

    // These variables hold the previous two elements of the vector.
    int x = 1;
    int y = 1;

    // Sets each element in the vector to the sum of the
    // previous two elements.
    generate_n(v.begin() + 2,
        elementCount - 2,
        [=]() mutable throw() -> int { // lambda is the 3rd parameter
        // Generate current value.
        int n = x + y;
        // Update previous two values.
        x = y;
        y = n;
        return n;
    });
    print("vector v after call to generate_n() with lambda: ", v);

    // Print the local variables x and y.
    // The values of x and y hold their initial values because
    // they are captured by value.
    cout << "x: " << x << " y: " << y << endl;

    // Fill the vector with a sequence of numbers
    fillVector(v);
    print("vector v after 1st call to fillVector(): ", v);
    // Fill the vector with the next sequence of numbers
    fillVector(v);
    print("vector v after 2nd call to fillVector(): ", v);
}
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18

자세한 내용은 generate_n 참조하세요.

constexpr 람다 식

Visual Studio 2017 버전 15.3 이상 (모드 이상에서 /std:c++17 사용 가능): 캡처되거나 도입된 각 데이터 멤버의 초기화가 상수 식 내에서 허용되는 경우 람다 constexpr 식을 (또는 상수 식에서 사용) 선언할 수 있습니다.

    int y = 32;
    auto answer = [y]() constexpr
    {
        int x = 10;
        return y + x;
    };

    constexpr int Increment(int n)
    {
        return [n] { return n + 1; }();
    }

결과가 함수의 constexpr 요구 사항을 충족하는 경우 람다는 암시적으로 constexpr 발생합니다.

    auto answer = [](int n)
    {
        return 32 + n;
    };

    constexpr int response = answer(10);

람다가 암시적 또는 명시적 constexpr이면 함수 포인터로 변환하면 함수가 constexpr 생성됩니다.

    auto Increment = [](int n)
    {
        return n + 1;
    };

    constexpr int(*inc)(int) = Increment;

Microsoft 전용

람다는 CLR(공용 언어 런타임) 관리되는 엔터티에서 지원되지 않습니다. ref classref structvalue classvalue struct

Microsoft 관련 한정자(예: __declspec)를 사용하는 경우 바로 뒤에 parameter-declaration-clause람다 식에 삽입할 수 있습니다. 예시:

auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };

특정 한정자가 람다에서 지원되는지 여부를 확인하려면 Microsoft 관련 한정자 섹션의 한정자에 대한 문서를 참조하세요.

Visual Studio는 C++11 표준 람다 기능 및 상태 비정상 람다를 지원합니다. 상태 비스테이션 람다는 임의의 호출 규칙을 사용하는 함수 포인터로 변환할 수 있습니다.

참고 항목

C++ 언어 참조
C++ 표준 라이브러리의 함수 개체
함수 호출
for_each