프로그래밍 언어/C & C++ 정리

C++ 람다 식

뽀또치즈맛 2024. 9. 15. 14:40

람다 식이란?

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

#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        //람다 표현식 시작
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
            // 람다 표현식 끝
        }
    );
}

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

 

  1. capture 절 (C++ 사양의 람다 소개자 라고도 함)
  2. 매개 변수 목록 선택 사항입니다. (람다 선언자라고도 함)
  3. 변경 가능한 사양 선택 사항입니다.
  4. exception-specification 선택 사항입니다.
  5. 후행 반환 형식 선택 사항입니다.
  6. 람다 본문입니다.

 

1.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] {};				// 문제 없음
	[&, &i] {};				// ERROR : &가 기본값일 때 i 앞에 &가 올 수 없음
    [=, this] {};			// ERROR : =이 기본값일 때 this가 명시적으로 캡처할 수 없음
	[=, * this] {};			// 문제 없음 : 캡처절의 this 를 값으로 캡처함
	[i, i] {};				// ERROR : i가 중복해서 캡처됨
}

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

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

 
클래스 멤버 함수의 본문에서 람다 식을 사용하려면 캡처 절에 포인터를 전달하는 this를 사용하여
바깥쪽 클래스의 멤버 함수 및 데이터 멤버에 대한 액세스를 제공합니다.
 
VS2017비전 15.3 이상에서는 this 캡처절에 지정하여 *this 포인터를 값으로 캡처할 수 있습니다.
값으로 캡처하면 람다가 호출되는 모든 호출 사이트에 전체 닫기를 복사합니다.
(클로저는 람다 식을 캡슐화하는 무명 함수 개체입니다.)
값별 캡처는 람다가 병렬 또는 비동기 작업에서 실행되는 경우에 유용합니다.
NUMA와 같은 특정 하드웨어 아키텍처에서 특히 유용합니다.
 
클래스 멤버 함수와 함께 람다 식을 사용하는 방법 예제는 다음 포스팅에서 다루도록 하겠습니다.
 

람다 본문

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

  • 앞의 설명대로 바깥쪽 범위에서 캡처된 변수
  • 매개 변수
  • 로커로 선언된 변수
  • 클래스 내에서 선언되고 캡처되는 경우 클래스 this 데이터 멤버
  • 정적 스토리지 기간이 있는 모든 변수 (ex: 전역 변수)
#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;
}

 
n 변수는 값별로 캡처되므로 람다 식을 호출한 후 해당 값이 0으로 유지됩니다.
즉 람다 식은 자동 스토리지 기간이 있는 변수만 캡처할 수 있습니다.
 

void fillVector(vector<int>& v)
{
    // 지역(static) 변수.
    static int nextValue = 1;

    // 아래의 generate 함수 호출에 등장하는 람다 표현식은
    // 지역 static 변수인 nextValue를 수정하고 사용한다.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    // 경고: 이 코드는 스레드 안전(thread-safe)하지 않으며, 
    // 예시를 위해서만 보여진다.
}

 
위 코드는 generate 함수 와 람다 식을 사용하여 vector 개체의 각 요소 값을 할당하는 것입니다.
람다 식은 정적 변수를 수정하여 요소 값을 생성할 수 있습니다.
 
 
해당 코드의 전문은 다음과 같습니다.

// 컴파일 옵션: /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)
{
    // 지역 static 변수
    static int nextValue = 1;

    // 아래의 generate 함수 호출에 등장하는 람다 표현식은
    // 지역 static 변수인 nextValue를 수정하고 사용한다.
    generate(v.begin(), v.end(), [] { return nextValue++; });
    // 경고: 이 코드는 스레드 안전(thread-safe)하지 않으며,
    // 예시를 위해서만 보여진다.
}

int main()
{
    // 벡터에 있는 요소의 개수
    const int elementCount = 9;

    // 모든 요소가 1로 설정된 벡터 객체를 생성
    vector<int> v(elementCount, 1);

    // 이 변수들은 벡터의 이전 두 요소 값을 저장
    int x = 1;
    int y = 1;

    // 벡터의 각 요소를 이전 두 요소의 합으로 설정
    generate_n(v.begin() + 2,
        elementCount - 2,
        [=]() mutable throw() -> int { // 람다가 세 번째 매개변수로 사용됨
        // 현재 값을 생성
        int n = x + y;
        // 이전 두 값을 업데이트
        x = y;
        y = n;
        return n;
    });
    print("generate_n() 호출 후 벡터 v: ", v);

    // 지역 변수 x와 y를 출력
    // x와 y의 값은 값으로 캡처되었기 때문에 초기 값을 유지
    cout << "x: " << x << " y: " << y << endl;

    // 벡터를 숫자들의 시퀀스로 채운다
    fillVector(v);
    print("fillVector() 첫 번째 호출 후 벡터 v: ", v);

    // 벡터를 다음 숫자들의 시퀀스로 채운다
    fillVector(v);
    print("fillVector() 두 번째 호출 후 벡터 v: ", v);
}

 
 
출저 및 참고 문서
https://learn.microsoft.com/ko-kr/cpp/cpp/lambda-expressions-in-cpp?view=msvc-170

C++ 람다 식

자세한 정보: C++의 람다 식

learn.microsoft.com

https://modoocode.com/253

씹어먹는 C ++ - <4 - 6. 클래스의 explicit 과 mutable 키워드>

모두의 코드 씹어먹는 C ++ - <4 - 6. 클래스의 explicit 과 mutable 키워드> 작성일 : 2018-12-26 이 글은 48965 번 읽혔습니다. 에 대해 다룹니다. 안녕하세요 여러분! 이번 강좌는 클래스에서 비교적 자주 쓰

modoocode.com

 
 

'프로그래밍 언어 > C & C++ 정리' 카테고리의 다른 글

* const 와 reference  (0) 2024.09.27
함수 뒤에 const? 너 누군데  (0) 2024.09.25
C++ 기억 존속 시간, 사용 범위  (0) 2024.08.01
템플릿  (0) 2024.07.31
파일 복사 프로그램  (0) 2024.07.09