C ++ 11에서 람다식이 란 무엇입니까?


1486

C ++ 11에서 람다식이 란 무엇입니까? 언제 사용합니까? 그들이 소개하기 전에 불가능했던 어떤 종류의 문제를 해결합니까?

몇 가지 예와 사용 사례가 유용 할 것입니다.


14
람다가 매우 유용한 경우를 보았습니다. 내 동료가 공간 최적화 문제를 해결하기 위해 수백만 회 반복하는 코드를 작성하고있었습니다. 적절한 함수보다 람다를 사용할 때 알고리즘이 훨씬 빠릅니다! 컴파일러는 Visual C ++ 2013입니다.
sergiol

답변:


1489

문제

C ++에는 std::for_eachand와 같은 유용한 일반 함수가 포함되어 std::transform있어 매우 편리합니다. 불행히도, 특히 적용하려는 펑터 가 특정 기능에 고유 한 경우 사용하기가 번거로울 수 있습니다 .

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

f한 번만 특정 장소에서 사용하는 경우 사소한 일을하기 위해 전체 클래스를 작성하는 것이 과도하게 보입니다.

C ++ 03에서는 functor를 로컬로 유지하기 위해 다음과 같은 것을 작성하고 싶을 수도 있습니다.

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

그러나 이것은 허용 f되지 않으며 C ++ 03 의 템플릿 함수 로 전달 될 수 없습니다 .

새로운 솔루션

C ++ 11에서는 람다를 사용하여 인라인 익명 펑터를 작성하여을 대체 할 수 struct f있습니다. 작은 간단한 예제의 경우 읽기가 더 깨끗하고 (모든 것을 한 곳에 유지) 유지하기가 가장 단순 할 수 있습니다 (예 : 가장 간단한 형식).

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

람다 함수는 익명의 functors에 대한 구문 설탕입니다.

반품 유형

간단한 경우 람다의 반환 유형이 다음과 같이 추론됩니다.

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

그러나 더 복잡한 람다를 작성하기 시작하면 컴파일러가 반환 유형을 추론 할 수없는 경우가 발생합니다.

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

이 문제를 해결하려면 다음을 사용하여 람다 함수에 대한 반환 유형을 명시 적으로 지정할 수 있습니다 -> T.

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

"캡처"변수

지금까지 우리는 람다에 전달 된 것 이외의 것을 사용하지 않았지만 람다 내에서 다른 변수를 사용할 수도 있습니다. 다른 변수에 액세스하려면 []지금까지 사용되지 않은 캡처 절 ( 표현식)을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

참조와 값을 모두 사용하여 캡처 할 수 있으며 &, =각각 과를 사용하여 지정할 수 있습니다 .

  • [&epsilon] 참조로 캡처
  • [&] 람다에 사용 된 모든 변수를 참조로 캡처
  • [=] 람다에 사용 된 모든 변수를 값으로 캡처
  • [&, epsilon] [&]와 같은 변수를 캡처하지만 값으로 엡실론
  • [=, &epsilon] [=]와 같은 변수를 캡처하지만 참조로 epsilon

생성 된 operator()것은 const기본적으로 캡처가 const기본적으로 액세스 할 때 캡처한다는 의미입니다 . 이는 동일한 입력을 가진 각 호출이 동일한 결과를 생성하는 효과가 있지만, 생성 된 호출 이 아닌 것을 요청 하도록 람다를 표시mutable 할 수 있습니다 .operator()const


9
@Yakk 당신은 갇혀있다. 캡처가없는 람다는 함수 유형 포인터로 암시 적으로 변환됩니다. 변환 기능은 const항상 ...
Johannes Schaub-litb

2
@ JohannesSchaub-litb oh sneaky-호출 할 때 발생 ()합니다-인수가 0 인 람다로 전달되지만 () const람다와 일치하지 않기 때문에 암시 적 캐스트를 포함하는 형식 변환을 찾습니다. 함수 포인터를 호출 한 다음 호출합니다! 교활한!
Yakk-Adam Nevraumont

2
흥미 롭다-나는 원래 람다는 펑터가 아닌 익명의 함수 라고 생각 했으며 캡처가 어떻게 작동했는지 혼란 스러웠다.
user253751

49
당신이 당신의 프로그램에서 변수로 람다를 사용하려면, 당신은 사용할 수 있습니다 std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; 그러나 일반적으로, 우리는 종류를 추론 컴파일러를 보자 auto f = [](int a, bool b) -> double { ... }; (그리고 잊지 마세요로 #include <functional>)
에버트 헤일 렌

11
return d < 0.00001 ? 0 : d;피연산자 중 하나가 정수 상수 일 때 (두 번째와 세 번째 피연산자가 일반적인 산술을 통해 서로 균형을 이루는? : 연산자의 암시 적 승격 규칙으로 인해) 두 배의 반환이 보장되는 이유를 모든 사람이 이해하지 못한다고 생각 합니다. 어느 것이 선택 되더라도 전환). 로 변경 0.0 : d하면 예제를보다 쉽게 ​​이해할 수 있습니다.
Lundin

829

람다 함수 란 무엇입니까?

람다 함수의 C ++ 개념은 람다 미적분 및 함수형 프로그래밍에서 시작됩니다. 람다는 명명되지 않은 함수로, 재사용이 불가능하고 명명 할 가치가없는 짧은 코드 조각에 유용합니다 (이론이 아닌 실제 프로그래밍에서).

C ++에서 람다 함수는 다음과 같이 정의됩니다

[]() { } // barebone lambda

또는 모든 영광에서

[]() mutable -> T { } // T is the return type, still lacking throw()

[]캡처 목록, ()인수 목록 및 {}함수 본문입니다.

캡처리스트

캡처 목록은 람다 외부에서 함수 본문 내에서 사용 가능한 항목과 방법을 정의합니다. 다음 중 하나 일 수 있습니다.

  1. 값 : [x]
  2. 참조 [& x]
  3. 참조에 의해 현재 범위 내에있는 변수 [&]
  4. 3과 동일하지만 값 [=]

쉼표로 구분 된 목록에서 위의 내용을 혼합 할 수 있습니다 [x, &y].

인수 목록

인수 목록은 다른 C ++ 함수와 동일합니다.

기능 본체

람다가 실제로 호출 될 때 실행될 코드입니다.

반품 유형 공제

람다에 return 문이 하나만 있으면 반환 유형을 생략 할 수 있고 암시 적 유형은 decltype(return_statement)입니다.

변하기 쉬운

람다가 변경 가능 (예 :)으로 표시 []() mutable { }되면 값으로 캡처 된 값을 변경하는 것이 허용됩니다.

사용 사례

ISO 표준에 의해 정의 된 라이브러리는 람다로부터 많은 이점을 얻었으며 이제는 사용자가 접근하기 쉬운 범위에서 작은 펑터로 코드를 어지럽 힐 필요가 없기 때문에 몇 가지 바의 유용성을 높입니다.

C ++ 14

C ++ 14에서는 람다는 다양한 제안으로 확장되었습니다.

초기화 된 Lambda 캡처

캡처 목록의 요소를 이제로 초기화 할 수 있습니다 =. 이를 통해 변수의 이름을 바꾸고 이동하여 캡처 할 수 있습니다. 표준에서 가져온 예 :

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

그리고 Wikipedia에서 가져온 것 중 하나를 사용하여 캡처하는 방법을 보여줍니다 std::move.

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

일반 람다

람다는 이제 일반화 auto될 수 있습니다 ( 주변 범위의 어딘가에 형식 템플릿 인수가있는 T경우 여기 와 같습니다 T).

auto lambda = [](auto x, auto y) {return x + y;};

리턴 타입 공제 개선

C ++ 14는 모든 함수에 대해 추론 된 리턴 유형을 허용하며이를 형식의 함수로 제한하지 않습니다 return expression;. 이것은 또한 람다로 확장됩니다.


2
위의 초기화 된 람다 캡처에 대한 예제에서 왜 람다 함수를 (); 이것은 [] () {} ()과 같습니다. 대신에 [](){};. 또한 x의 값이 5가 아니어야합니까?
Ramakrishnan Kannan 2016 년

6
@RamakrishnanKannan : 1))은 람다를 정의 한 직후에 호출하고 반환 값을 y로 제공합니다. 변수 y는 람다가 아닌 정수입니다. 2) 아니요, x = 5는 람다 (외부 스코프 변수 x와 동일한 이름을 갖는 값에 의한 캡처)에 대해 로컬 인 다음 x + 2 = 5 + 2가 반환됩니다. 외부 변수 x의 재 할당은 참조 r :을 통해 발생 r = &x; r += 2;하지만 원래 값은 4입니다.
Vee

167

Lambda 식은 일반적으로 알고리즘을 캡슐화하여 다른 함수로 전달 될 수 있도록 사용됩니다. 그러나 정의 즉시 람다를 실행할 수 있습니다 .

[&](){ ...your code... }(); // immediately executed lambda expression

기능적으로

{ ...your code... } // simple code block

람다 식은 복잡한 함수를 리팩토링하기위한 강력한 도구입니다 . 위와 같이 코드 섹션을 람다 함수로 래핑하여 시작합니다. 그런 다음 각 단계 후에 중간 테스트를 통해 명시 적 매개 변수화 프로세스를 점진적으로 수행 할 수 있습니다. 코드 블록을 완전히 매개 변수화하면 (을 제거하여 설명 &) 코드를 외부 위치로 이동하여 정상적인 기능으로 만들 수 있습니다.

마찬가지로 람다 식을 사용 하여 알고리즘 결과에 따라 변수초기화 할 수 있습니다 ...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

프로그램 로직을 분할하는 방법 , 당신도 유용 다른 람다 식의 인수로 람다 식을 전달하는 찾을 수 있습니다 ...

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

Lambda 식을 사용하면 명명 된 중첩 함수 를 만들 수 있습니다.이 함수 는 중복 논리를 피하는 편리한 방법입니다. 명명되지 않은 람다를 사용하면 사소하지 않은 함수를 다른 함수에 매개 변수로 전달할 때 눈에 약간 더 쉬운 경향이 있습니다 (익명 인라인 람다에 비해). 참고 : 닫는 중괄호 뒤에 세미콜론을 잊지 마십시오.

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

후속 프로파일 링에서 함수 객체에 대한 초기화 오버 헤드가 현저한 경우이를 일반 함수로 다시 작성하도록 선택할 수 있습니다.


11
이 질문이 1.5 년 전에 요청되었고 마지막 활동이 거의 1 년 전에 이루어 졌다는 것을 알고 있습니까? 어쨌든, 당신은 내가 전에 보지 못한 흥미로운 아이디어를 제공하고 있습니다!
Piotr99

7
동시 정의 및 실행 팁에 감사드립니다! 나는 그것의 가치를위한 contidion 등의 그 작품 것을주의 생각 if: 문 if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace, 가정이 i이다std::string
의 Blacklight 빛나는

74
따라서 다음은 올바른 표현 [](){}();입니다..
nobar

8
어! 파이썬의 (lambda: None)()구문은 훨씬 더 읽기 쉽습니다.
dan04

9
@ nobar-당신이 맞아요, 잘못 입력했습니다. 이것은 합법적이다 (나는이 시간을 테스트)main() {{{{((([](){{}}())));}}}}
마크 Lakata에게

38

대답

Q : C ++ 11에서 람다식이 란 무엇입니까?

A : 후드 아래에서 오버로드 연산자 () const 가있는 자동 생성 클래스의 객체입니다 . 이러한 객체를 클로저 라고 하며 컴파일러에서 생성합니다. 이 '클로저'개념은 C ++ 11의 바인드 개념에 가깝습니다. 그러나 람다는 일반적으로 더 나은 코드를 생성합니다. 클로저를 통한 호출은 완전한 인라인을 허용합니다.

Q : 언제 사용합니까?

A : "간단하고 작은 논리"를 정의하고 이전 질문에서 생성을 수행하도록 컴파일러에 요청합니다. 컴파일러에게 operator () 내부에 포함시킬 식을 제공합니다. 다른 모든 것들 컴파일러가 당신에게 생성 할 것입니다.

Q : 소개 이전에는 불가능했던 어떤 종류의 문제를 해결합니까?

A : 커스텀 추가, subrtact 연산을 위한 함수 대신 연산자 오버로딩과 같은 일종의 구문 설탕입니다 . 그러나 불필요한 클래스를 더 많이 저장하여 1-3 개의 실제 로직을 일부 클래스 등에 래핑합니다. 일부 엔지니어는 줄 수가 적 으면 오류가 발생할 가능성이 적다고 생각합니다 (저도 그렇게 생각합니다)

사용 예

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

질문에 포함되지 않은 람다에 대한 추가 정보. 관심이 없으면이 섹션을 무시하십시오.

1. 캡처 된 값. 캡처 할 수있는 것

1.1. 정적 저장 시간이 람다 인 변수를 참조 할 수 있습니다. 그들은 모두 붙잡 혔습니다.

1.2. "값별"캡처 값에 람다를 사용할 수 있습니다. 이러한 경우 캡처 된 var는 함수 객체 (클로저)에 복사됩니다.

[captureVar1,captureVar2](int arg1){}

1.3. 당신은 참조를 캡처 할 수 있습니다. &-이 문맥에서 포인터가 아닌 참조를 의미합니다.

   [&captureVar1,&captureVar2](int arg1){}

1.4. 모든 비 정적 변수를 값 또는 참조로 캡처하는 표기법이 있습니다.

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5. 정적이 아닌 모든 var를 값 또는 참조로 캡처하고 smth를 지정하는 표기법이 있습니다. 더. 예 : 모든 정적이 아닌 변수를 값으로 캡처하지만 참조 캡처로 Param2

[=,&Param2](int arg1){} 

정적이 아닌 모든 var를 참조로 캡처하지만 값 캡처로 Param2

[&,Param2](int arg1){} 

2. 반품 유형 공제

2.1. 람다가 하나의 표현식 인 경우 람다 리턴 유형을 추론 할 수 있습니다. 또는 명시 적으로 지정할 수 있습니다.

[=](int arg1)->trailing_return_type{return trailing_return_type();}

람다에 둘 이상의 표현식이있는 경우 후행 리턴 유형을 통해 리턴 유형을 지정해야합니다. 또한 자동 함수 및 멤버 함수에 유사한 구문을 적용 할 수 있습니다.

3. 캡처 된 값. 캡처 할 수없는 것

3.1. 객체의 멤버 변수가 아닌 로컬 변수 만 캡처 할 수 있습니다.

4. Сonversions

4.1 !! Lambda는 함수 포인터가 아니며 익명 함수가 아니지만 캡처리스 람다는 암시 적으로 함수 포인터로 변환 될 수 있습니다.

추신

  1. 람다 문법 정보에 대한 자세한 내용은 Programming Language C ++ # 337, 2012-01-16, 5.1.2 실무 초안에 나와 있습니다. 람다 식, p.88

  2. C ++ 14에는 "init capture"라는 추가 기능이 추가되었습니다. 클로저 데이터 멤버를 임의로 선언 할 수 있습니다.

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};

이것은 [&,=Param2](int arg1){}올바른 구문 될 것 같지 않습니다. 올바른 형식은 다음과 같습니다[&,Param2](int arg1){}
GetFree

감사. 먼저이 스 니펫을 컴파일하려고했습니다. 캡처 목록의 허용 가능한 수정 자에서 이상한 아 시메트리가 보입니다. // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; 자동 f = [=, & param] (int arg1) 변경 가능 {param = arg1;}; f (111); printf ( "% i \ n", 매개 변수); } #endif #if 0 {int param = 0; 자동 f = [&, = param] (int arg1) 변경 가능 {param = arg1;}; f (111); printf ( "% i \ n", 매개 변수); } #endif return 0; }
bruziuz

주석에서 새 줄이 지원되지 않는 것으로 보입니다. 그런 다음 5.1.2 Lambda 표현식, p.88, "작업 초안, 프로그래밍 언어 C ++ 표준", Dcoument Number : # 337, 2012-01-16을 열었습니다. 그리고 문법 구문을 살펴 보았습니다. 그리고 네 말이 맞아 "= arg"를 통한 캡처와 같은 것은 존재하지 않습니다.
bruziuz

큰 감사, 설명에 고정하고 새로운 지식을 얻습니다.
bruziuz

16

람다 함수는 인라인으로 만드는 익명 함수입니다. 일부 설명 된대로 변수를 캡처 할 수 있지만 (예 : http://www.stroustrup.com/C++11FAQ.html#lambda ) 몇 가지 제한 사항이 있습니다. 예를 들어 이와 같은 콜백 인터페이스가 있다면

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

아래에 적용하기 위해 전달 된 것과 같은 기능을 사용하기 위해 그 자리에 함수를 작성할 수 있습니다.

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

그러나 당신은 이것을 할 수 없습니다 :

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

C ++ 11 표준의 한계 때문에 캡처를 사용하려면 라이브러리를 사용하고

#include <functional> 

(또는 간접적으로 가져 오는 알고리즘과 같은 다른 STL 라이브러리) 다음 일반 함수를 다음과 같은 매개 변수로 전달하는 대신 std :: function으로 작업하십시오.

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}

1
그 이유는 람다는 캡처가 없으면 함수 포인터로만 변환 할 수 있기 때문입니다. applyfunctor를 수락 한 템플릿 인 경우 작동합니다
sp2danny

1
그러나 문제는 apply가 기존 인터페이스 인 경우 일반 이전 함수와 다르게 선언 할 수 없다는 것입니다. 이 표준은 그러한 람다식이 실행될 때마다 캡처 된 변수에 대한 하드 코딩 된 참조가 생성 된 평범한 이전 함수의 새로운 인스턴스가 생성되도록 설계되었을 수 있습니다. 컴파일 타임에 람다 함수가 생성되는 것 같습니다. 다른 결과도 있습니다. 예를 들어 정적 변수를 선언하면 람다 식을 다시 평가하더라도 새로운 정적 변수를 얻지 못합니다.
Ted

1
함수 포인터는 종종 저장되며 람다 캡처는 범위를 벗어날 수 있습니다. 만 캡처없는 람다는 함수 포인터로 변환하는 것이이 디자인했다
sp2danny

1
여전히 같은 이유로 할당 해제되는 스택 변수에주의를 기울여야합니다. blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… 참조 출력과 적용으로 쓴 예제는 함수 포인터가 허용되고 사용 된 경우에도 작동하도록 작성되었습니다. 적용에서 모든 함수 호출이 완료 될 때까지 col은 할당 된 상태로 유지됩니다. 기존 apply 인터페이스를 사용하여이 코드를 어떻게 다시 작성 하시겠습니까? 전역 또는 정적 변수를 사용하거나 코드를 좀 더 모호하게 변환 하시겠습니까?
Ted

1
또는 단순히 람다식이 rvalues이므로 일시적이지만 코드는 일정하게 유지되므로 (단일 / 정적) 나중에 호출 할 수 있습니다. 이 경우 스택 할당 캡처가 할당 된 한 함수가 할당 된 상태를 유지해야합니다. 물론 예를 들어 함수의 많은 변형이 루프에 할당되면 풀기 어려워 질 수 있습니다.
Ted

12

가장 좋은 설명 중 하나는 그의 책 11 장 ( ISBN-13 : 978-0321563842 ) 에서 lambda expressionC ++ Bjarne Stroustrup의 저자가 제공 한 것입니다 .***The C++ Programming Language***

What is a lambda expression?

람다 식 , 때로는 또한라고도 람다 A와 함수 또는 (엄밀히 말하자면 부정확하지만 놓고) , λ는 , 정의 및 사용하기위한 간략화 된 표기법 익명 함수 개체 . operator ()로 명명 된 클래스를 정의하고 나중에 해당 클래스의 객체를 만들고 마지막으로 호출하는 대신 속기를 사용할 수 있습니다.

When would I use one?

이것은 연산을 알고리즘에 인수로 전달할 때 특히 유용합니다. 그래픽 사용자 인터페이스 (및 다른 곳)와 관련하여 이러한 작업을 종종 콜백 이라고합니다 .

What class of problem do they solve that wasn't possible prior to their introduction?

여기에서 람다 식으로 수행 된 모든 작업을 처리하지 않고도 해결할 수 있지만 훨씬 더 많은 코드와 훨씬 더 복잡한 것으로 생각합니다. 람다 식 이것은 코드를 최적화하는 방법이며 더 매력적으로 만드는 방법입니다. Stroustup에 의해 슬프게도 :

효과적인 최적화 방법

Some examples

람다 식을 통해

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

또는 기능을 통해

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

또는

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

U가 필요한 경우 lambda expression다음과 같이 이름을 지정할 수 있습니다.

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

아니면 다른 간단한 샘플을 가정

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

다음에 생성됩니다

0

1

0

1

0

1

0

1

0

1

0 정렬 x-1; x-3; x-4; x-5; x-6; x-7; x-33;

[]-이것은 캡처 목록이거나 로컬 환경에 액세스 할 필요가없는 lambda introducer경우 lambdas사용할 수 있습니다.

책에서 인용 :

람다 식의 첫 문자는 항상 [ 입니다. 람다 소개자는 다양한 형식을 취할 수 있습니다.

[] : 빈 캡처 목록입니다. 이는 람다 본문에서 주변 컨텍스트의 로컬 이름을 사용할 수 없음을 의미합니다. 이러한 람다 식의 경우 데이터는 인수 또는 로컬이 아닌 변수에서 가져옵니다.

[&] : 암시 적으로 참조로 캡처합니다. 모든 로컬 이름을 사용할 수 있습니다. 모든 지역 변수는 참조로 액세스됩니다.

[=] : 값으로 암시 적으로 캡처합니다. 모든 로컬 이름을 사용할 수 있습니다. 모든 이름은 람다 식의 호출 시점에서 가져온 로컬 변수의 복사본을 나타냅니다.

[캡처 목록] : 명시 적 캡처; capture-list는 참조 또는 값으로 캡처 할 (즉, 오브젝트에 저장되는) 로컬 변수의 이름 목록입니다. 이름 앞에 &가 붙은 변수는 참조로 캡처됩니다. 다른 변수는 값으로 캡처됩니다. 캡처 목록에는이 이름과 요소 다음에 ...가 포함될 수 있습니다.

[&, capture-list] : 목록에 언급되지 않은 이름을 가진 모든 지역 변수를 암시 적으로 참조하여 캡처합니다. 캡처 목록에이를 포함 할 수 있습니다. 나열된 이름 앞에는 &를 사용할 수 없습니다. 캡처 목록에 이름이 지정된 변수는 값으로 캡처됩니다.

[=, capture-list] : 목록에 언급되지 않은 이름을 가진 모든 로컬 변수를 값으로 암시 적으로 캡처합니다. 캡처 목록에는이를 포함 할 수 없습니다. 나열된 이름 앞에 &가 와야합니다. 캡처 목록에 명명 된 변수는 참조로 캡처됩니다.

&로 시작하는 로컬 이름은 항상 참조로 캡처되며 &로 시작하지 않는 로컬 이름은 항상 값으로 캡처됩니다. 참조로만 캡처하면 호출 환경에서 변수를 수정할 수 있습니다.

Additional

Lambda expression 체재

여기에 이미지 설명을 입력하십시오

추가 참조 :


좋은 설명입니다. 사용 범위 기반 루프, 당신은 람다를 방지하고 코드를 단축 할 수for (int x : v) { if (x % m == 0) os << x << '\n';}
디트리히는 문헌 [Baumgarten

2

글쎄, 내가 찾은 실용적인 용도는 보일러 판 코드를 줄이는 것입니다. 예를 들면 다음과 같습니다.

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

람다가 없으면 다른 bsize경우 를 위해 무언가를해야 할 수도 있습니다 . 물론 함수를 만들 수 있지만 영혼 사용자 함수의 범위 내에서 사용을 제한하려면 어떻게해야합니까? 람다의 본질은이 요구 사항을 충족하며 그 경우에 사용합니다.


2

C ++의 람다는 "사용 가능한 기능"으로 처리됩니다. 예, 문자 그대로 이동 중에 정의합니다. 사용해; 부모 함수 범위가 끝나면 람다 함수가 사라집니다.

c ++은 c ++ 11에 도입되었으며 모든 사람들이 가능한 모든 장소에서 사용하기 시작했습니다. 예제와 람다는 무엇입니까 https://en.cppreference.com/w/cpp/language/lambda

나는 거기에 없지만 어떤 C ++ 프로그래머에게 알아야 할지를 설명 할 것입니다.

Lambda는 모든 곳에서 사용하기위한 것이 아니며 모든 기능을 lambda로 바꿀 수는 없습니다. 또한 일반 기능과 비교할 때 가장 빠르지는 않습니다. 람다에 의해 처리되어야하는 약간의 오버 헤드가 있기 때문입니다.

경우에 따라 줄 수를 줄이는 데 도움이 될 것입니다. 기본적으로 코드 섹션에 사용할 수 있습니다. 코드 섹션은 동일한 함수에서 한 번 이상 호출되며 독립 실행 형 함수를 만들 수 있도록 다른 코드가 필요하지 않습니다.

다음은 람다의 기본 예와 배경에서 발생하는 일입니다.

사용자 코드 :

int main()
{
  // Lambda & auto
  int member=10;
  auto endGame = [=](int a, int b){ return a+b+member;};

  endGame(4,5);

  return 0;

}

컴파일이 확장하는 방법 :

int main()
{
  int member = 10;

  class __lambda_6_18
  {
    int member;
    public: 
    inline /*constexpr */ int operator()(int a, int b) const
    {
      return a + b + member;
    }

    public: __lambda_6_18(int _member)
    : member{_member}
    {}

  };

  __lambda_6_18 endGame = __lambda_6_18{member};
  endGame.operator()(4, 5);

  return 0;
}

보시다시피, 사용할 때 어떤 종류의 오버 헤드가 추가됩니까? 모든 곳에서 사용하는 것은 좋지 않습니다. 적용 가능한 장소에서 사용할 수 있습니다.


예, 문자 그대로 이동 중에 정의합니다. 사용해; 부모 함수 범위가 끝나면 람다 함수가 사라집니다 . 함수가 람다를 호출자에게 반환하면 어떻게됩니까?
Nawaz

1
또한 일반 기능과 비교할 때 가장 빠르지는 않습니다. 람다에 의해 처리되어야하는 약간의 오버 헤드가 있기 때문입니다. 이 주장을 뒷받침하기 위해 실제로 벤치 마크 실행 한 적이 있습니까? 반대로 람다 + 템플릿은 종종 가장 빠른 코드를 생성합니다.
Nawaz

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.