C ++ 11에서 람다식이 란 무엇입니까? 그들이 소개하기 전에

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

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



답변

문제

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


답변

람다 함수 란 무엇입니까?

람다 함수의 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;. 이것은 또한 람다로 확장됩니다.


답변

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);

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


답변

대답

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);};

답변

람다 함수는 인라인으로 만드는 익명 함수입니다. 일부 설명 된대로 변수를 캡처 할 수 있지만 (예 : 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');
    });
}

답변

가장 좋은 설명 중 하나는 그의 책 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 체재

추가 참조 :


답변

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

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