본문 바로가기
놀기/C, C++

C++ 람다 식 공부 (1)

by Hi~ 2021. 7. 21.

많은 언어에서 람다(lambda) 식 표현을 사용한다. 임베디드 계열의 작업을 주로 하는 난 어색하기만 하다. 그리고 딱히 쓸 일도 없다. 그런데, Cross-Compiler들이 지원하는 C/C++ 버전이 올라가면서 익숙하지 않은 문법을 사용하거나 lambda 식을 Library가 늘어나고 있다. 이와 같은 새로운 문법에 바로바로 적응하는 사람들이 대단하다고 생각한다. 어쨌든 직접 코딩에 쓰지는 않더라도 코드를 읽기 위해서는 공부할 수밖에 없는 실정이다. 하니씩 공부해 보자. 

 

사람마다 스타일이 있겠지만 학교 수업도 아니고 A-Z 방식의 공부는 하지 않는다.

그리고, 맨땅에 헤딩할 수는 없으니 Microsoft의 자료를 기반으로 첨언하는 방식을 사용해서 공부를 하자. (번역기로 돌린 문서라고 한다. 그래서 한글이 간혹 어색하다. 일부는 수정함.)


Korean : https://docs.microsoft.com/ko-kr/cpp/cpp/lambda-expressions-in-cpp?view=msvc-160
English : https://docs.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-160

 


 

C++ 람다 식

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

● C++ 은 ISO가 2011년 8월 12일에 승인한 C++ 프로그래밍 언어의 최신판이다. 게시물 작성 시점에서 최신 버전은 C++20이고 C++23 spec. 이 정리 중이다. 아이고 부지런해라. ㅎ
● 익명 함수 개체(클로저)를 정의하는 편리한 방법 : 예를 들어 sort 구현 시, 두 개의 값을 비교하는 함수를 작성해야 하는데 이러한 함수는 일회용이라 만들긴 해야 하는데 좀 거시기하다. 이러한 경우 사용한다.

 


 

람다 식의 일부

ISO C++ 표준에서는 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
    );
}


이 그림에서는 람다의 부분을 보여 줍니다.


1) capture 절(C++ 사양에서 람다 소개자라고도 함)
2) 매개 변수 목록 선택적. (람다 선언자라고도 함)
3) 변경 가능한 사양 선택적.
4) exception-specification 선택적.
5) trailing-return-type 선택적.
6) 람다 본문입니다.

●  1)과 6)은 필수 값이고 나머지는 사용하는 방식에 따라 필요하면 사용할 수 있는 구문이다. 이것만으로는 이해하기 어려우니 앞으로 나올 예제를 통해 이해하자.

 


 

Capture 절

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

● 위에서 언급한 "1) capture" 절이다.
● 갑자기 C++14가 튀어나오네.. ㅡ.ㅡ;; C++14는 C++11의 사소한 확장으로, 주된 특징으로는 수많은 버그 수정 및 약간의 개선 사항이 있었다고 위키에 나온다. 
● capture라는 단어 뜻에 맞게 외부의 변수를 람다 본문에서 사용할 수 있는데 이와 관련된 설명이다. 그냥 쓸 경우 변수 복사가 되고 앰퍼샌드(&)가 앞에 붙을 경우 참조가 된다.

 

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

●  [] 안에 아무것도 없으면 말 그대로 바깥쪽 범위의 변수에 액세스 하지 않는다는 뜻이다.

 

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

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

 

● 뭔 말인지 어려워진다. 
● [=]으로 표시될 경우, 모든 변수가 값(복사)으로 capture 된다. 
● [&]으로 표시될 경우, 모든 변수가 참조로 capture 된다. 
● 외부 변수 total에 참조 별로 액세스하고 외부 변수 factor에 값 별로 액세스 하는 경우 위의 6개는 동일한 의미라고 한다. 자세히 보면 모든 변수가 참조이고 factor가 값으로 지정되거나 모든 변수가 값으로 지정되는데 total만 참조로 지정되는 것임을 알 수 있다.

 

capture-default를 사용하는 경우 람다 본문에 언급된 변수만 캡처됩니다.

 

capture 절에 캡처 기본 가 포함된 경우 해당 캡처 & 절의 캡처에 형식의 식별자가 없을 수 &identifier 있습니다. 마찬가지로 capture 절에 capture-default 가 포함된 경우 = 해당 캡처 절의 캡처는 형식일 수 =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
}
● 위의 설명을 바탕으로 보면 이해할 수 있다.
● [&, i] : 모두 참조로 하되 i는 값으로 한다.
● [&, &i] : 모두 참조이므로 i를 다시 참조로 할 수 없다. (ERROR)
● [=, this] : 모두가 값이므로 this를 다시 값으로 할 수 없다. (ERROR)
●[=, *this] : 모두 값으로 하되 this는 포인터(참조)로 한다. this를 이와 같이 넘기면 멤버 함수 및 데이터 멤버에 대한 액세스가 가능하다.
● [i, i] : i가 반복되었다. (ERROR)

 

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

 

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

 

● 줄임표도 사용 가능하다.

 

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

● 위에서 언급한 내용 PASS


Visual Studio 2017 버전 15.3 이상(/std:c++17에서 사용 가능): 캡처 this 절에서 를 지정하여 포인터를 값으로 캡처할 수 *this 있습니다. 값으로 캡처는 람다가 호출되는 모든 호출 사이트에 전체 클로저를 복사합니다. 클로저는 람다 식을 캡슐화하는 익명 함수 개체입니다. 값으로 캡처는 람다가 병렬 또는 비동기 작업으로 실행되는 경우에 유용합니다. NUMA와 같은 특정 하드웨어 아키텍처에서 특히 유용합니다.
클래스 멤버 함수와 함께 람다 식을 사용하는 방법을 보여 주는 예제는 람다 식의 예에서"예제: 메서드에서 람다 식 사용"을 참조하세요.
capture 절을 사용하는 경우 특히 다중 스레딩과 함께 람다를 사용하는 경우 이러한 점을 염두에 두는 것이 좋습니다.

 

  • 참조 캡처를 사용하여 외부에서 변수를 수정할 수 있지만 값 캡처는 수정할 수 없습니다. mutable(에서는 복사본을 수정할 수 있지만 원본은 수정할 수 없습니다.)
  • 참조 캡처는 외부 변수에 대한 업데이트를 반영하지만 값 캡처는 그렇지 않습니다.
  • 참조 캡처는 수명 종속성을 발생시키지만 값 캡처에는 수명 종속성이 없습니다. 람다가 비동기적으로 실행되는 경우 특히 중요합니다. 비동기 람다에서 참조로 로컬을 캡처하는 경우 람다가 실행될 때까지 해당 로컬이 쉽게 사라질 수 있습니다. 코드로 인해 런타임에 액세스 위반이 발생할 수 있습니다.
● 2018년 기준으로 C++17은 C++의 ISO/IEC 14882 표준 중 가장 최신 개정판이라고 한다. 그냥 넘어가자.
● 프로그래밍을 하시는 분이면 대충 뭔 말인지 아실 것 같다. 그냥 넘어가자.

 

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

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

pNums = make_unique<vector<int>>(nums);
//...
auto a = [ptr = move(pNums)]()
{
   // use ptr
};
● move-only variables이 이동 전용 변수로 번역되었다.
● https://modoocode.com/301 를 참조하면 자세한 자료가 있다. 
● 그냥 간단히 memory move 하듯이 객체를 move 한다고 보면 될 것 같은데, 좀 더 추후에 좀 더 공부하자.

 

'놀기 > C, C++' 카테고리의 다른 글

C++ 람다 식 공부 (6) - Examples  (0) 2021.07.23
C++ 람다 식 공부 (5) - Examples  (0) 2021.07.23
C++ 람다 식 공부 (4) - Examples  (0) 2021.07.23
C++ 람다 식 공부 (3)  (0) 2021.07.22
C++ 람다 식 공부 (2)  (0) 2021.07.22

댓글