상세 컨텐츠

본문 제목

프로그래밍 언어 람다식 표현 관련

본문

반응형




//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://psychoria.tistory.com/entry/C-11-%EC%9D%B4%EB%A6%84-%EC%97%86%EB%8A%94-%ED%95%A8%EC%88%98-%EB%9E%8C%EB%8B%A4Lambda1


C++11에서 새롭게 추가된 문법은 람다입니다.

람다는 그리스어 알파벳의 11번째 글자로 <λ> 이런 모양으로 생겼습니다.

이미 이전 C++11 포스팅([C++11] Range-Based For Loop)에서 람다를 사용한 for_each 구문을 사용한 적이 있었습니다.

람다는 익명 함수(Anonymous Function)이라고도 부릅니다.

함수의 몸체(Body)는 있지만 이름(Name)이 없기 때문입니다.

람다는 STL의 함수 객체(Function Object 혹은, Functor)처럼 동작합니다.

함수 객체처럼 암시적으로 함수 객체 클래스를 만들고, 함수 객체를 생성해서 전달합니다.

혹시나 함수 객체를 모르시는 분은 STL을 공부하시는 것을 추천합니다.

람다는 함수 포인터와 함수 객체에 비해 다음과 같은 장점이 있습니다.

함소 포인터가 상태를 유지하는 게 불가능하고(전역 변수를 통해서 억지로는 가능합니다.)

함수 객체는 별도의 클래스를 정의해야 하는 불편함이 있습니다.

람다는 이 둘의 단점을 보완해서 장점만을 취합니다.

코드 상으로 클래스를 별도로 구현하지도 않고, 상태의 유지가 가능합니다.

람다의 구조는 다음과 같습니다.(MSDN 참조)


기존의 C++에서는 볼 수 없었던 특이한 문법을 갖고 있습니다.

순서대로 각각의 구성은 다음과 같습니다.

1. lambda-introducer(캡처(Capture) 부분)
2. lambda-parameter-declaration-list(인자 목록)
3. mutable-specification
4. exception-specification
5. lambda-return-type-clause(리턴 타입)
6. compound-statement(람다 구현 부분)

간단하게 C++11 람다의 문법에 대해서 확인해 보았는데요.

함수의 선언부에 들어갈 내용을 빼고 함수의 몸체는 기존의 함수와 크게 다른 게 없어 보입니다.

다음 글에서는 람다 문법을 자세하게 설명하고 예제로 확인하겠습니다.

이름 없는 함수, 람다(Lambda)(2) 링크는 다음과 같습니다.

2014/12/11 - [Programming/C++11&14] - [C++11] 이름 없는 함수, 람다(Lambda)(2)



출처: http://psychoria.tistory.com/entry/C-11-이름-없는-함수-람다Lambda1 [냉정과 열정사이]




//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



출처: http://z3moon.com/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/cpp0x/lambda



Lambda Expression

이번 C++0x 에 추가된 가장 임팩트있느 기능 중 하나이다. 유용하고 흥미로운건 사실이지만 복잡하기도 하다. 일단 기본적인 문법과 예를 들어 설명을 시작하겠다. 처음 시작하는 아래 몇개의 Lambda 코드샘플들이 쓸모없어 보일수도 있지만 분명한 것은 Lambda 는 그 간결함에 비해 매우 강력하다는 것이다.

시작하기 전 이걸 먼저 기억하자.

Lambda 는 지역적으로 선언된 함수와도 같다. 표현식이 사용되거나 함수가 호출될 수 있는 곳에서는 어디든지 Lambda 를 사용할 수 있다.

일단 가장 기본적인 Lambda 를 보자.

[]{};

위 코드는 C++0x 에서 완벽하게 작동되는 코드이다. [] 는 Lambda-introducer 라고 부르며 컴파일러에게 이후에 따라오는 expression/code 가 lambda 임을 알려준다. {} 는 Lambda 의 정의부이며 일반 함수의 그것과 같다. 위에 정의한 lambda 는 어떠한 argument 도 없고 어떠한 값도 return 하지 않으며 아무런 동작도 하지 않는다.

이제 확장해보자.

double PI = []{ return 3.14159; }();        // 뒤에 () 를 추가함으로써 함수호출 형태를 갖는다.
double PI = [](){ return 3.14159; }();      // 앞에 () 를 추가하였으며 아무 argument 도 갖지 않는다.
double PI = [](void){ return 3.14159; }();  // 바로 위와 같은 의미

함수 호출을 위해 뒤에 괄호가 붙어야 하며, 파라메터가 없다면 앞에 괄호는 생략 가능하다. C++ 표준 committee 는 Lambda 가 덜 복잡하길 원하며 그래서 생략 가능토록 디자인하였다.

이제 파라메터를 사용하는 경우를 보자.

bool is_even = []( int n ){ return n % 2 == 0; }( 41 );

첫번째 괄호 ( int n ) 는 parameter 를 나타낸다. 그리고 뒤에 괄호 ( 41 ) 는 Lambda 호출을 위한 실제 argument 를 의미한다. 위 본문은 파라메터 n 이 짝수인지 여부를 반환한다. 이제 max, min 를 구하는 Lambda 를 구현해보자.

int nMax = []( int n1, int n2 ) {
    return ( n1 > n2 ) ? n1 : n2;
} ( 56, 11 );
 
int nMin = []( int n1, int n2 ) {
    return ( n1 < n2 ) ? n1 : n2;
} ( 984, 658 );

위와 같이 여러개의 파라메터를 사용하는 Lambda 함수를 한줄로 표현할 수 있게 되었다. 두개의 파라메터를 받아서 하나를 리턴한다. 비슷하게 여러 파라메터를 받게할 수도 있다.

이제 몇가지 의문사항이 생길텐데..

  • 리턴타입은 int 만 가능한가?
  • lambda 가 하나의 return statement 로 표현 불가능한 경우엔? 예를 들어 return 이전에 몇개의 선행 라인이 있어야 하는 경우.
  • lambda 가 value 를 출력하거나 하는 등의 다른 행위가 필요한 경우엔?
  • 정의된 lambda 의 reference 를 저장하여 나중에 재사용할 수 있는가?
  • lambda 가 다른 lambda 나 function 을 호출할 수 있는가?
  • lambda 는 local-function 으로써 저장된다. 그렇다면 function 을 넘나들며 사용할 수 있는가?
  • lambda 는 그 자신이 정의된 영역의 변수에 접근이 가능하며 수정도 가능한가?
  • default arguments 를 지원하나?
  • function pointer 와 functor 와 뭐가 다른가?

이 질문들에 답하기 전에 Lambda Expression 문법에 대한 설명이 있는 아래 이미지를 먼저 보자.


리턴타입은 int 만 가능한가?

→ operator 다음에 return 타입을 정의할 수 있다. 예를 들어

pi = []()->double{ return 3.14159; }();

위 lambda 는 return statement 한줄로 이루어져 있기 때문에 명시적으로 return 타입을 쓸 필요는 없다. 따라서 →double 는 생략이 가능하다.

이번엔 이 예를 보자.

int nAbs = [] (int n1) -> int
{
    if ( n1 < 0 )
        return -n1;
    else
        return n1;
}( -109 );

위 lambda 는 하나 이상의 statement 를 이용하였기에 반드시 명시적으로 return 타입을 써야 한다. 만약 →int 를 생략하면 아래의 에러가 뜬다.

error 3499: a lambda that has been specified to have a void return type cannot return a value

컴파일러는 lambda 본문의 내용이 return statement 하나루 이루어져 있지 않다면 기본적으로 void 리턴형으로 가정한다. 따라서 반드시 명시적으로 써 주어야 한다.

아래와 같이 리턴 타입은 어느것이든 될 수 있다.

[]() -> int* {}
[]() -> std::vector<int>::const_iterator& {}
[]( int x ) -> decltype( x ) {}     // x 로부터 타입을 추론

반면 배열이나 auto 를 리턴타입으로 가질 수 없다.

[]() -> float[] {};       // error C2090: function returns array
[]() -> auto {};          // error C3558: 'auto': a lambda return type cannot contain 'auto'

물론 lambda 에 의해 반환된 값을 auto 에 대입할 수는 있다.

auto pi = [] { return 3.14159; } ();
auto nSum = [] ( int n1, int n2, int n3 ) { return n1 + n2 + n3; } ( 10, 20, 70 );
auto xVal = [] ( float x ) -> float
{
    float t
    t = x * x / 2.0f;
    return t;
} ( 44 );

만약 파라메터가 없기에 괄호() 를 생략한 상태에서 명시적 return 타입을 쓴다면 에러가 발생한다.

[] -> double { return 3.14159; } ();   // 에러!! []() -> double {...} 와 같이 반드시 괄호를 써줘야 함

lambda 가 하나의 return statement 로 표현 불가능한 경우엔?

위 설명만으로 일반적인 함수가 가질 수 있는 표현들을 lambda 에 담기에 충분하다. lambda 는 function/method 가 가질 수 있는 어떠한 것이던 포함할 수 있다. local/static 변수, 다른 함수를 호출하고 메모리를 할당하고 다른 lambda 를 호출하는 것 까지! 아래의 코드는 터무니 없어보여도 분명 유효한 코드이다.

[]()
{
    static int stat = 99;
 
    class TestClass
    {
    public:
        int member;
    };
 
    TestClass test;
    test.member = labs( -100 );
 
    int* ptr = [] ( int n1 ) -> int*
    {
        int* p = new int;
        *p = n1;
        return p;
    } ( test.member );
 
    delete ptr;
};

정의된 lambda 의 reference 를 저장하여 나중에 재사용할 수 있는가? lambda 는 local-function 으로써 저장된다. 그렇다면 function 들 사이에서 사용될 수 있는가?

짝수인지 아닌지를 판별하는 lambda 를 정의해보자. auto 키워드를 쓰면 lambda 를 변수로 저장할 수 있다. 그리고 이 변수로 lambda 를 호출할 수 있다. lambda 의 타입은 나중에 논의해보기로 한다.

auto IsEven = [] ( int n ) -> bool
{
    if ( n % 2 == 0 )
        return true;
    else
        return false;
}; // lambda 를 호출한 것이 아니다. 괄호() 가 없는 것을 보면 알 수 있듯이..

추론할 수 있듯이 위 lambda 는 인자를 받아서 bool 타입을 리턴한다. 그리고 중요한 것은 lambda 를 호출한 것은 아니라 정의만 했다는 것이다. 만약 괄호() 와 인자를 저 위 코드에 추가했더라면 auto 타입은 bool 이 되었을 것이다. (lambda 타입이 아닌!) 이제 지역적으로 정의된 함수와 마찬가지로 나중에 호출할 수 있다.

IsEven( 20 );
 
if ( !IsEven( 45 ) )
    std::cout << "45 is not even";

위에 있는 IsEven 정의부는 그 아래 두번의 호출한 부분과 지역적으로 같은 곳에 있다. 만약 다른 함수에서 호출하고자 하면 어떻게 해야할까? 여러가지 접근법이 있겠다. 예를 들어 local 또는 class 레벨의 변수에 저장하고 다른 함수의 인자로 넘긴다던가 (function pointer 같이) 또는 global 영역에 함수를 정의하는 방법도 있다. 아직 lambda 의 변수타입을 논의하지 않았기 때문에 전자의 경우는 나중에 얘기하기로 하고 일단 후자의 경우 (global 에 정의) 를 먼저 살펴보자.

// lambda 의 리턴타입은 bool 이고, lambda 는 IsEven 에 auto 로써 저장된다.
auto IsEven = [] ( int n ) -> bool
{
    if ( n % 2 == 0 ) return true;
    else return false;
}
 
void AnotherFunction()
{
    IsEven( 10 );
}
 
int main()
{
    AnotherFunction();
    IsEven( 10 );
}

auto 키워드는 local 또는 global 영역에서만 동작하기에 위 예시처럼 global 변수에 lambda 를 저장할 수 있다. 나중에 클래스 변수에 저장하기 위해서는 lambda 의 정확한 타입을 알 필요가 있겠다.

앞서 언급한 대로 lambda 는 거의 일반 function 처럼 동작한다. 따라서 변수를 출력한다거나 하는 등의 행위도 역시 할 수 있다.

int main()
{
    using namespace std;
 
    auto DisplayIfEven = [] ( int n ) -> void
    {
        if ( n % 2 == 0 )
            std::cout << "Number is even\n";
        else
            std::cout << "Number is odd\n";
    };
 
    cout << "Calling lambda...";
 
    DisplayIfEven( 40 );
}

하나 중요한 것은 지역적으로 정의된 (locally-defined) lambda 는 상위 scope 에 정의된 namespace resolution 을 얻지 못한다는 것이다. 따라서 위 예에서 DisplayIfEven 함수 안에서 std namespace 가 유효하지 않기에 명시적으로 std:: 를 써줘야 한다.

lambda 가 다른 lambda 나 function 을 호출할 수 있는가?

function 과 마찬가지로 가능하다.

lambda 가 default arguments 를 지원하는가?

지원하지 않는다.

lambda 가 그 자신이 정의되거나 호출된 곳에 있는 변수로 접근이 가능한가? 변수를 수정할 수 있는가? function pointer 와 function objects (functors) 와 다른점은?

이제 지금까지 언급하지 않았던 Capture Specification 에 대해 논의해보자.

Lambda 는 다음중 하나가 될 수 있다.

  • Stateful
  • Stateless

state 는 상위 영역 (이하 upper-scope) 에 있는 변수를 capture 하는 방식에 대한 정의이다. 나는 그것들을 다음과 같은 카테고리로 분류한다.

  1. upper-scope 로부터의 어떠한 변수도 접근하지 않는다. 지금까지 설명해온 방식이 여기에 해당한다.
  2. upper-scope 변수에 read-only 모드로 변수에 접근한다.
  3. 변수가 lambda 로 복사되어지고 (같은 이름으로) 이 복사본을 수정할 수 있다. 함수 호출에 call-by-value 와 같은 원리이다.
  4. upper-scope 변수에 full accessibility 로 접근한다. 물론 수정도 가능하다.

이 네가지 카테고리는 다음의 C++ 특성들에서 따온 것이다.

  1. 변수는 private 이고, 따라서 외부에서 접근할 수 없다.
  2. const 멤버 함수에선 변수를 수정할 수 없다.
  3. 변수가 함수에 넘겨질 때 passed-by-value 로 넘어간다.
  4. 함수에 passed-by-reference 로 넘어갈 경우 full accessibility 를 가진다.

이제 capture 해보자. 위에서 언급한 capture specification 은 [] 안에서 정의된다. 아래의 문법은 capture-specification 을 나타낸다.

  • [] - 아무것도 capture 하지 않는다.
  • [=] - 모든 것을 value 로써 capture 한다.
  • [&] - 모든 것을 reference 로써 capture 한다.
  • [var] - var 를 value 로써 capture 한다. 이 외 다른 것들은 아무것도 capture 하지 않는다.
  • [&var] - var 를 reference 로써 capture 한다. 이 외 다른 것들은 아무것도 capture 하지 않는다.

예1:

int a = 10, b = 20, c = 30;
 
[a] (void)     // 'a' 만 value 로써 capture 한다.
{
    std::cout << "Value of a=" << a << std::endl;
 
    // 수정할 수 없다.
    a++;   // error C3491: 'a': a by-value capture cannot be modified in a non-mutable lambda
 
    // 다른 변수엔 접근할 수 없다.
    std::cout << b << c;  // error C3493: 'b' cannot be implicitly captured because no default capture mode has been specified
}();

예2:

auto Average = [=] () -> float        // '=' 는 모든 것을 value 로써 capture 한다는 뜻
{
    return ( a + b + c ) / 3.0f;  // 모든걸 value 로써 capture 하지만 수정할 수는 없다.
}
 
float x = Average();

예3:

auto ResetAll = [&] () -> void
{
    a = b = c = 0; // reference 로써 capture 했기에 수정 가능하다.
};
 
ResetAll();

= 는 by-value 를 선언하는 것이고, & 는 by-reference 를 선언하는 것이다. 좀 더 살펴보자. 줄여쓰기 위해 앞으로는 auto 변수에 lambda 를 저장하지 않고 바로 호출할 것이다.

예4:

// a, b 를 by value 로 capture 한다.
// 그리고 파라메터가 없으므로 () 생략 가능하다.
int nSum = [a,b]
{
    return a + b;
} ();
 
std::cout << "Sum: " << nSum;

위에서 보듯이 여러 capture specification 을 동시에 사용할 수 있다. 이걸 응용해서 a, b, c 를 합하여 nSum 에 대입하는 예를 작성해 보겠다.

예5:

// 모든걸 by-value 로 capture 한 후, nSum 만 by-reference 로 capture 한다.
[=, &nSum]
{
    nSum = a + b + c;
} ();

위 예에서 = 로 모든걸 by-value 로 capture 한 부분은 default capture mode 에 해당하고 &nSum 는 그 위에 override 를 의미한다. default capture mode 는 다른 capture 이전에 와야 한다. 즉, = 또는 & 와 같이 모든 변수를 대상으로 하는 선언이 단일 변수를 대상으로 한 선언보다 앞에 와야 한다. (제일 앞에 와야 한다는 얘기다) 따라서 아래 예는 에러를 뱉는다.

[&nSum, =] {};  // 에러!!
[a,b,c,&] {};

예를 몇 개 더 보자.

[&, b] {};             // (1) 전부 by-reference 지만 'b' 만 by-value
[=, &b] {};            // (2) 전부 by-value 지만 'b' 만 by-reference
[b, c, &nSum] {};      // (3) b, c, 는 by-value, c 는 by-reference, 다른것은 capture 하지 않는다.
[=] ( int a ) {};      // (4) 전부 by-value 이고 원래의 'a' 변수는 파라메터 'a' 에 의해 가려진다! 문법상 오류가 아니므로 주의해서 사용하자.
[&, a, c, nSum] {};    // 결과적으로 (2) 와 같다.
[b, &a, &c, &nSum] {}; // 결과적으로 (1) 과 같다.
[=, &] {};             // error C3409: empty attribute block is not allowed
[&nSum, =] {};         // error
[a, c, b, &] {};       // error

지금까지 특정 변수가 capture 되는 여러가지 경우를 보아 왔다. 특정 변수가 capture 되지 않도록 하는 것, by-value 로 하되 const 로써 수정되지 않도록 capture 하는 것, by-reference 로 capture 하는 것 들을 보아왔다. 이것들은 각각 (위에서 열거한 내용) 1, 2, 4 번에 해당하는 것들이다. 이제 마지막으로 2번에 해당하는 call-by-value 모드를 보자.

mutable 키워드

파라메터를 넣는 괄호 바로 다음에 mutable 이라는 키워드를 지정할 수 있다. 이 키워드가 있다면 by-value 로 capture 하는데다 수정까지 할 수 있다. mutable 키워드를 쓰지 않으면 기본적으로 by-value 는 const 속성을 가진다. 즉 capture 해온 변수를 lambda 안에서 수정할 수 없다는 뜻이다. mutable 을 지정해 줌으로써 컴파일러에게 capture 해온 복사본 변수를 수정할 수 있게 해달라고 부탁할 수 있다. 그리고 by-value 로 capture 한 변수들 중 선택적으로 const, non-const 를 따로 지정할 수는 없다. 이렇게 하고 싶다면 간단하게 그냥 파라메터로 넘기는 방법을 쓰는게 좋다.

예:

int x = 0, y = 0, z = 0;
 
[=] () mutable -> void  // mutable 을 지정할 경우 괄호() 가 필수적이다.
{
    x++;  // mutable 이 지정되었기에 by-value 로 capture 해온 변수를 수정 가능하다.
} ();
// 하지만 x 는 여전히 0 이다.

위에서 lambda 호출이 끝난 후에도 x 의 값은 여전히 0이다. function 호출의 call-by-value 와 마찬가지로 lambda 안에서 x 는 복사본으로 취급되기 때문이다.

lambda 가 function-pointer 나 functor 와 다른점은?

funtion-pointer 는 자체 상태state 를 가질 수 없지만 lambda 는 상태를 가질 수 있다. by-reference 로 capture 하면 lambda 는 호출간 상태를 유지할 수 있다. 이것은 function 으로썬 할 수 없고 function-pointer 는 타입에 안전하지 않고 에러를 유발하기 쉽다. 그리고 calling-convention 과 복잡한 문법을 필요로 한다.

functor 는 물론 상태를 매우 잘 유지할 수 있다. 그러나 아주 사소하고 작은 코드조각을 위해서라도 class 를 작성하고 그 안에 변수를 넣고 operator () 를 정의해야 한다는 부담이 있다. 그리고 중요한 것은 이 functor 를 다른 함수에 사용하기 위해선 현재 함수의 바깥에 정의해두어야 한다. 이것은 코드의 흐름 (역주:가독성을 말하는 듯) 을 깨버린다.

lambda 의 타입은 무엇인가?

lambda 는 사실상 class 이다. 이 lambda 를 function class 오브젝트에 저장할 수 있다. std::tr1 네임스페이스에 이것이 정의되어 있다. 예를 보자.

#include <functional>
 
...
 
std::function<bool(int)> IsEven = [] ( int n ) -> bool
{
    return n % 2 == 0;
};
IsEven( 23 );

<bool(int)> 는 function 클래스의 template 파라메터이다. 대충 보는 바와같이 bool 형을 리턴하고 int 형 인자를 받는 함수라는 것을 의미한다. lambda 를 function object 로써 (functor) 저장할 때 타입캐스팅에 유의해야 한다. 그렇지 않으면 컴파일러가 에러나 경고를 뱉기 때문이다. 그러나 지금까지 보아왔듯이 auto 키워드를 이용하면 편하게 사용할 수 있었다.

하지만! 반드시 function 타입을 정확하게 칭해야 할 때가 있다. 이 function object 를 함수의 인자로써 넘길때이다. 예를 보자.

using namespace std;
 
void TakeLambda( function<void(int)> lambda ) // 여기서 auto 타입을 사용할 수 없다.
{
    lambda( 32 );
}
 
TakeLambda( DisplayIfEven ); // 위에서 정의했던 lambda 를 인자로 넣어 호출

DisplayIfEven 이라는 lambda 는 int 타입 파라메터를 받고 아무것도 return 하지 않는다. TakeLambda 함수는 이 function object 를 파라메터로 받으며, 함수 내에서 lambda 를 호출한다.

C++ 에서 lambda 는 어떤 의미인가?

lambda 는 많은 STL 함수들 (특히 function-pointer 나 function-object 를 필요로 하는) 에게 있어서 매우 유용하다. 간단히 말해 lambda 는 callback 함수가 필요한 경우 유용하게 사용될 수 있다. 여기서 STL 함수들에 대해 다루진 않겠지만 lambda 의 간략하고 이해하기 쉬운 형태에 대해서는 소개하겠다. non-STL 를 사용한 예는 일단 불필요하고 모양새가 이상하다. 그러나 여기서 그에 대한 해결책을 소개하겠다.

예를 들어 아래의 함수는 function 이 인자로써 필요하다. 함수 내에서 인자로 넘어온 파라메터로 다시 함수를 호출한다. int 형 인자를 받고 void 를 return 하는 형태의 function-pointer, function-object, 또는 lambda 중 어느것이든 아래 함수의 인자로써 사용될 수 있다.

void CallbackSomething( int nNumber, function<void(int)> callback_function )
{
    callback_function( nNumber );
}

그리고 이 CallbackSomething 을 호출하는 세가지 다른 유형을 보여주겠다.

// 그냥 함수
void IsEven( int n )
{
    std::cout << ( ( n % 2 == 0 ) ? "Yes" : "No" );
}
 
// operator () 를 오버라이드한 class. 일명 functor
class Callback
{
public:
    void operator() ( int n )
    {
        if ( n < 10 )
            std::cout << "Less than 10";
        else
            std::cout << "More than 10";
    }
};
 
int main()
{
    // function-pointer 사용
    CallbackSomething( 10, IsEven );
 
    // function-object 사용
    CallbackSomething( 23, Callback() );
 
    // lambda 사용
    CallbackSomething( 59, [] ( int n ) { std::cout << "Half:" << n / 2; } );
}

이제 N 보다 큰 숫자인 경우에만 출력을 하는 Callback 이란 함수를 만들어보자.

class Callback
{
    /*const*/ int Predicate;
 
public:
    Callback( int nPredicate ) : Predicate( nPredicate ) {}
 
    void operator() ( int n )
    {
        if ( n < Predicate )
            std::cout << "Less than " << Predicate;
        else
            std::cout << "More than " << Predicate;
    }
};
 
// function object 사용
CallbackSomething( 23, Callback( 24 ) );
 
// 좀 다른 방법
Callback obj( 99 );
CallbackSomething( 44, obj );

이걸 호출하기 위해 단지 기준되는 숫자를 가지고 생성자를 인자로 넣어주면 된다. 원본 CallbackSomething 함수는 바꿀 필요가 없는 것이다.

이 방법에서 Callback 이란 클래스에 현재 state 를 유지하는 기능을 추가하였다. 이 class 인스턴스가 살아있는 한 그 state 역시 계속 유지된다. 그러므로 만약 이 인스턴스를 가지고 여러 CallbackSomething 함수에 사용했더라도 같은 state 를 사용할 것이다. 알겠지만 이것은 function-pointer 에서는 할 수 없는 것이다. 물론 또 다른 인자를 추가하고 받아들이게끔 하면 가능은 하지만 별로 예쁜 모습은 아닐 것이다. 만약 특정 함수에서 내부에서 호출 가능한 어떠한 것 (function-pointer or functor 등) 을 파라메터로 요구되는 경우 그 타입을 명확히 지정해야 하고 반드시 그 타입의 것을 인자로 넣어야 한다. 그리고 function-pointer 는 state 를 가질 수 없고 보통 이런 시나리오에서 유용하지 않다.

그럼 lambda 는 어떤가? 이전에 언급했던대로 lambda 는 capture specification 을 통해 state 를 가질 수 있다. 그래서 lambda 는 이러한 유동적인 state 기능을 이용해서 위 시나리오 해결이 가능하다. 여기 예시를 보자.

int Predicate = 40;
 
// state 를 가지는 lambda
auto stateful = [Predicate] ( int n )
{
    if ( n < Predicate )
        std::cout << "Less than " << Predicate:
    else
        std::cout << "More than " << Predicate;
};
 
CallbackSomething( 59, stateful );  // more than 40 을 확인
 
Predicate = 1000;
CallbackSomething( 100, stateful ); // lambda 에서 Predicate 는 변하지 않고 여전히 more than 40 을 확인한다.

함수 안에서 지역적으로 정의된 stateful 한 lambda 는 function-object (functor) 보다 간결하고 function-pointer 보다 깔끔하다. 그리고 state 로 가진다.

위 예에서 첫번째 함수 호출에서 “More than 40” 이 출력된다. 그리고 두번째 호출에서도 여전히 같은 결과가 출력된다. 중간에 Predicate 에 1000 을 대입하여 state 를 바꾸길 의도했지만 그렇지 못했다. by-value 로 capture 했기 때문에 lambda 안에서는 복사본이 생성되었기 때문이다. 따라서 외부의 state 수정이 lambda 에도 영향을 미치게 하려면 by-reference 로 capture 해야 한다.

auto stateful = [&Predicate] ( int n )  // by-reference 로 capture

만약 functor 로 이를 구현하려면 SetPredicate 등의 멤버함수를 class 에 추가하여 해결 가능하겠다.

STL 과의 조합

for_each STL 함수는 컨테이너 안에 특정 range 에 해당하는 element 들에게 특정 함수를 적용한다. template 을 이용하기때문에 인자로써 어떤 타입이라도 대입될 수 있다. 이제 lambda 로 예를 들어 설명해보자. 간략화 하기 위해 list 나 vector 대신 순수 array 를 사용하겠다.

using namespace std;
 
int Array[ 10 ] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 
for_each( Array, &Array[ 10 ], IsEven );
 
for_each( Array, Array + 10, [] ( int n ) { std::cout << n << std::endl; } );

첫번째로 IsEven 함수를 호출하고 두번째로 for_each 안에 정의된 lambda 를 호출한다. 원소가 10개이기 때문에 각각은 함수를 10번씩 호출한다. for_each 두번째 파라메터가 같은 의미라는건 구지 말하지 않겠다. (근데 말하고 말았네..)

이건 for_each 와 lambda 를 이용하여 값을 출력하는 매우 간단한 예이다. 구지 별도의 function 이나 class 를 작성하지도 않았다. 확실히 lambda 는 부가적인 일을 위해 쉽게 확장 가능하다. 솟수만 출력한다던가 합을 계산하거나 특정 영역range 의 원소element 를 수정한다던가 등의 일 말이다.

lambda 인자를 수정하기

음, 물론 가능하다. 좀 더 말해보자면, 지금까지는 변수를 by-reference 로 capture 하여 수정하는것은 말했지만 lambda 의 인자argument 를 수정하는 것은 말하지 않았다. 지금까지 그 필요성을 못 느꼈기 때문이다. 어쨌든 가능하고 그렇게 하려면 lambda 의 파라메터를 reference 로써 (혹은 pointer) 선언하면 된다.

// lambda 의 파라메터 n 이 reference 로 선언됨
for_each( Array, Array + 10, [] ( int &n ) { n *= 4; } );

위 for_each 는 배열의 각 원소element 들에 4를 곱한다.

lambda 를 for_each 와 함께 사용하는 방법에 대해 설명했던것과 같이 <algorithm> 에 있는 다른 함수들, transform, generate, remove_if 등과도 역시 조합하여 사용 가능하다. lambda 는 STL 알고리즘에 한정되어 있지 않고 function-object 가 필요로 하는 곳에 효과적으로 활용이 가능하다. 정당한 숫자와 인자 타입만 지정해주고 혹시 인자 수정이 필요한지와 같은 것만 주의해주면 된다. 이 article 이 STL 이나 template 을 다루지는 않기에 더 이상 언급하진 않겠다.

lambda 는 function-pointer 와 같이 사용할 수 없다.

좀 헷갈리고 실망스러울 수 있지만 사실이다. function-pointer 를 필요로 하는 함수에 lambda 를 대신 사용할 수 없다. 간단한 예를 들어 내가 무얼 말하려는지 보여주겠다.

// int 를 인자로 받는 함수
typedef void (*DISPLAY_ROUTINE)(int);
 
// 함수 포인터를 받는 함수
void CalculateSum( int a, int b, DISPLAY_ROUTINE pfDisplayRoutine )
{
    pfDisplayRoutine( a + b );
}

CalculateSum 은 DISPLAY_ROUTINE 이라는 함수 포인터를 받는다. 아래 코드는 동작할 것이다.

void Print( int x )
{
    std::cout << "Sum is: " << x;
}
 
int main()
{
    CalculateSum( 500, 300, Print );
}

그러나 아래 코드는 잘못되었다.

CalculateSum( 10, 20, [] ( int n ) { std::cout << "Sum is: " << n; } );
// C2664: 'CalculateSum' : cannot convert parameter 3 from '`anonymous-namespace'::<lambda1>' to 'DISPLAY_ROUTINE'

왜일까? lambda 는 객체지향object-oriented 이고 사실은 class 이기 때문이다. 컴파일러는 내부적으로 lambda 를 위해 class 모델을 생성한다. 그리고 내부적으로 operator () 를 오버로드하게끔 생성하고 특정 data member 들을 생성한다. (이건 capture-specification 과 mutable-specification 으로 추측할 수 있다.) 물론 이 class 오브젝트가 function-pointer 로 변형될 수 없는 것이다.

이전의 예를 실행 가능하게 하는 방법은?

글쎄.. std::function 이라는 똑똑한 class 덕분에 위의 CallbackSomething 이 (함수포인터가 아닌) 함수를 인자로써 받을 수 있었다. for_each 는 std::function 을 취하지 않지만 template 을 사용한다. 그리고 내부적으로는 세번째 파라메터에 괄호를 붙여 호출하는 형태를 취한다.

template <class Iterator, class Function>
void for_each( Iterator first, Iterator, Function func )
{
    // 이 부분은 first 부터 두번째 파라메터까지 loop 하는 로직이라고 가정한다.
    // func 은 일반 함수이거나 operator () 를 가지는 클래스일 수 있다.
    func( first );
}

find, count_if 등의 다른 STL 함수들도 위와 비슷하게 동작하고 따라서 function-pointer, function-object, lambda 의 경우 모두 동작할 것이다.

따라서 lambda 를 SetTimer, EnumFontFamilies 등의 API 의 인자로써 사용하고자 한다면 그만 두는게 좋다. 강제 타입캐스팅을 한다고 하더라도 동작하지 않을 것이기 때문이다. 크래시가 날 뿐이다.



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



출처: http://tt91.tistory.com/11


최근에 보는 책이 있는데 람다함수 정리가 간단하게 잘되어있어서 따로 정리하였음 +ㅁ+

가능하면 최신 컴파일러를 사용하시는 것이 좋을듯?????

람다함수는 C++11부터 지원되었는데 이후 몇몇 부분이 수정되기도 했었기때문에 컴파일러에 따라 안되는 부분이 있을수도 있수도 있고 나중에 몇몇 부분이 바뀔수도 있다...


1. 람다 함수 정의 및 문법


람다함수의 기본적인 구조는 아래와 같음


[변수 캡쳐](받을 인자)->리턴타입{함수}(넘길 인자) 


[변수캡쳐]는 현재 함수에서 사용할 외부 변수들을 뜻함

main함수에 int num;이라는 변수가 있다면 그 변수를 사용하기 위해서는 변수 캡쳐를 사용하여야 한다

변수 캡쳐에 =를 넣으면 해당 함수의 모든 변수를 전부다 사용한다는 의미

&을 넣으면 모든 변수를 참조형으로 받아들인다는 의미이다

[=, &num]처럼 특정 변수만 참조형으로 사용하는 것도 가능

비워두면 아무것도 사용하지 않는다는 뜻

참고로 전역변수는 캡쳐를 해줄 필요가 없다


(받는 인자) ​부분은 말 그대로 함수에서 받는 인자들이다

일반적으로 int add(int a, int b); 선언할때 괄호 안에 있는 a,b 변수처럼 인자로 값을 받을 타입들을 지정해주는 것이다


->리턴 타입 ​이것도 말 그대로 리털해주는 타입을 지정해주는 것

void일경우 화살표와 함께 생략할 수 있다


{함수} 함수 몸체영역이다

int add(int a, int b)

{

return a + b;

}

같은 함수가 있으면 {}안의 영역과 똑같음


(넘길 인자) 호출하는 함수에서 넘겨주는 값들이다

add(10, 20);으로 함수를 호출할때 10, 20을 넣어주는 것처럼 넣어주면 된다



2. 람다 함수 중복


걍 람다함수안에 람다함수를 넣을 수 있음

람다함수안에 람다함수를 넣을때는 저렇게 간단하게 넣는게 좋을꺼 같다



3. 클래스의 맴버함수에 있는 람다함수


클래스 맴버함수에 람다함수가 들어있을 경우 따로 변수 캡쳐를 [=]으로 지정해서 사용하면 this포인터를 이용해 변수영역에 접근하는 방식이다

이 방식은 참조방식이기 때문에 따라서 [&]이나 [&this]를 넣을경우 에러가 뜬다라고 책에는 써져있는데 VS2015로 테스트해본 결과 [&]은 잘된다;;

노트북에 설치되어있는 VS2013에서도 해봤는데 잘된다

개발 환경에 따라 안될수도??



4. 람다함수를 포인터로


함수포인터로 다루는 방법은 두가지가 있다 auto를 사용하거나 std::function을 사용하는 것이다

위에 스샷은 졸업작품때 auto를 직접 사용하여 씬 전환을 간단히 표현 한 것이다



이런식으로 std::function을 사용하여 함수포인터로 활용할 수도 있다

functional 헤더를 선언 해준다음 std::function<반환형(파라미터 타입)> 방식으로 선언해주면 된다

함수포인터를 선언할때는 {}뒤에 있는 인자값을 받는 ()을 생략해도 된다



출처: http://tt91.tistory.com/11 [티티의 게임 & 개발 블로그]





//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://egloos.zum.com/frompt/v/2767779



▣ 람다 캡쳐 (Lambda Capture)

 

: 람다를 사용할 때 람다 외부에 있는 변수를 내부에서 사용하고 싶을 때 캡쳐를 쓴다.
: 캡쳐는 참조 및 변수들 (포인터 변수 포함)을 전달받을 수 있다.

★ 람다 캡쳐의 사용법

→ 람다의 처음 부분인 []의 내부에 외부에서 쓸 변수 이름을 기술하여 캡쳐가 가능하다. (복사 캡쳐)

int nCount = 3;
auto funcWithCapture =
[nCount]
{
    printf( "nCount = %d\n", nCount );
};
funcWithCapture();


→ 변수 앞에 &을 붙여서 참조로 캡쳐도 가능하다.



int nCount2 = 30;
auto funcWithRefCapture =
[&nCount2]
{
    nCount2++;
};
funcWithRefCapture();



→ 포인터 변수도 캡쳐 가능하다.



int nCount3 = 300;
int *pnPtr = &nCount3;
auto funcWithPtrCapture =
[pnPtr]
{
    (*pnPtr)++;
};
funcWithPtrCapture();


→ 람다 캡쳐는 일반 함수의 매개변수와 마찬가지로 값을 넘기는 것 보다는 (복사 캡쳐) 포인터나 참조를 캡쳐하는 것이 속도 측면에서 빠르다.
(물론 포인터 혹은 참조가 가리키는 객체 or 변수 or 배열 등의 크기가 크면 클수록 그 효율성은 더욱 커진다.)


→ 또한 포인터나 참조를 캡쳐할 시 내부에서 값을 변경할 수 있고 그것은 람다를 호출한 후에도 적용이 된다.
복사 캡쳐의 경우는 우선은 일반적인 방식으로 값 변경 시에는 컴파일 에러가 나고 다른 방법을 써야 하는데 그것은 밑에서 설명하겠다.
그러나 이 방법 또한 람다 내부에서만 변경 가능하고 람다 리턴시 값은 호출 이전 시점으로 돌아온다.
즉 복사 캡쳐로는 값을 바꿔서 외부에 적용시킬 수 있는 방법은 없다.


→ 여러 개의 변수도 물론 캡쳐가 가능하다.


[nCount1, nCount2]
{ … };


: 단순히 ‘,’(콤마)로 구분만 해주면 된다.


★ mutable 키워드


→ 람다 캡쳐로 외부의 값을 넘겨줄 시 람다 내부에서 그 값을 변경하면 컴파일 에러가 난다. 
( 복사로 캡쳐하는 경우 그렇다. 참조 및 포인터(가리키는 값)의 경우는 컴파일 에러가 나지 않는다. )
(단, 포인터 복사 캡쳐의 경우에도 포인터 자체를 변경하고자 할 시 컴파일 에러가 나며, 이것 또한 mutable로 해결 가능하다.)



int nValue = 100;
auto funcNoMutable =
[nValue]
{
    nValue++;        (X) // mutable을 쓰지 않고 복사 캡쳐에 대한 값 변경 불가
}


→ 하지만 mutable 키워드를 써주면 람다 내부에서에만 한해서 값 변경이 가능하다.
단, 사용시 람다의 인수가 없더라도 반드시 ()를 써줘야 하며, mutable 키워드는 인수 부분인 () 바로 직후에 쓰며, 
만약 명시적 리턴 값을 지정해 주는 ‘-> type’이 있을 시 그 이전부분에 써준다.



#include <stdio.h>
 
void main( void )
{
    int nValue = 100;
    auto funcWithMutable = 
    [nValue]()mutable -> int
    {
        nValue++;
        return 0;
    };
 
    printf( "Before lambda : nValue = %d\n", nValue );
    funcWithMutable();
    printf( "After  lambda : nValue = %d\n", nValue );
}


→ 확실히 알아둬야 할 점은 위의 함수가 호출 되도 복사 캡쳐를 한 nValue는 람다 내부에서만 변할 뿐 람다 함수를 벗어나면 nValue 값은 호출 이전 상태로 돌아온다는 것을 명심해야 한다.
(결국 외부 변수의 값을 람다를 통해 바꾸기 위해서는 포인터나 참조를 사용할 수밖에 없다.)


→ 위의 코드에 대한 결과는 이와 같다.





★ default 캡쳐


→ 람다 외부의 사용 가능한 모든 변수 (즉, 사용범위 내에 들어있는 변수들. 다시 말해 람다 선언 직전에 이미 선언되었고 소멸되지 않은 모든 변수들)를 참조로 혹은 복사로 한꺼번에 캡쳐할 수 있다.
사용법은 간단하다. 
모든 변수를 참조 캡쳐하고 싶으면 [] 안에 &을 → [&]
모든 변수를 복사 캡쳐하고 싶으면 [] 안에 =을 → [=]
붙이면 된다.


→ 그리고 만약 일부 몇몇 변수는 복사로 캡쳐하고, 나머지 모든 변수들은 참조로 캡쳐하고 싶다면 & 이후 복사 하고자 하는 변수 명들을 콤마로 분류하여 적어주면 된다.
: [&, n1, n2, …]
반대로 일부 몇몇 변수는 참조로 캡쳐하고, 나머지 모두 복사로 캡쳐하고 싶다면 처음에 =을 넣고 그 다음에 참조하고자 하는 변수들의 앞에 &을 붙여서 써주면 된다.
: [=, &n1, &n2, …]




int n1, n2, n3, n4, n5;
 
[&]{};    // n1~n5까지 모든 변수들을 참조로 캡쳐
[=]{};    // n1~n5까지 모든 변수들을 복사로 캡쳐
 
[&, n1, n2]{};    // n3, n4, n5는 참조, n1, n2는 복사로 캡쳐
[=, &n1, &n2]{};  // n3, n4, n5는 복사, n1, n2는 참조로 캡쳐
 
[n1, n1]{};   (X) // 에러. 같은 변수를 사용 
[&, &n1]{};   (X) // 에러. 디폴트 참조 캡쳐를 사용하는데 n1을 참조로 캡쳐
[=, n1]{};    (X) // 에러. 디폴트 복사 캡쳐를 사용하는데 n1을 복사로 캡쳐


→ 위 코드를 보면 알다시피 다음 세 가지에 주의하자.


1) 같은 변수를 다중으로 복사 혹은 참조 캡쳐해서는 안된다.
2) 디폴트 참조 캡쳐를 쓰는데 일부 다른 변수에 대해 또 참조 캡쳐해서는 안된다.
3) 디폴트 복사 캡쳐를 쓰는데 일부 다른 변수에 대해 또 복사 캡쳐해서는 안된다.


 


▷ 오늘은 람다 캡쳐 부분만 포스팅 하겠습니다. 생각보다 너무 졸리네요 =.=
다음 부분에는 람다 마지막 부분으로 람다의 응용부분에 대해서 쓰고 C++ 0x 포스팅을 끝마치도록 하겠습니다.


※ 참고자료 출처 : Microsoft Visual Studio 「Visual C++ 10과 C++0x」 ( 최흥배님 저 )



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



출처: http://choiarchive.blogspot.kr/2014/07/c-lambda.html

C++ lambda는 정말 끝내주는구나.....

리스너나 콜백에 전달할때 정말 편하다.
자바의 이름없는 클래스보다 더 편한것 같다.

어떤 함수에 동작되야할 내용을 주욱 작성하다가, 다 작성하고 나니, 이게 애니메이션 이벤트가 끝내고 실행되야할 내용이란걸 알았다.

그냥 심플하게 람다로 묶어버리고, 함수포인터로 저장한다음에 콜백으로 전달했다.

...ㅎㄷㄷ 진짜 편함.


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://cie.kunsan.ac.kr/programmer/3358



원문 링크 : http://carstart.tistory.com/m/183

참고 문헌 : http://itguru.tistory.com/196

이 글은 저를 기준으로 간단하게 원문을 요약한 글입니다.

원문에 들어가시면 보다 더 이해하기 쉬울 수 있습니다.

 

인라인 함수는 왜 쓸까요?

함수를 호출하고 나면 그 함수를 호출한 시점으로 되돌아가기 위해 리턴 주소를 스택에 보관하게 됩니다.

한마디로, 함수를 호출할 때마다 약간의 오버헤드는 발생한다는 것입니다.

 

이를 막기 위해서는 함수에 inline 지정자를 사용합니다.

이는 리턴 주소를 스택에 보관하지 않고 함수 코드를 Ctrl C, V 하는 식으로 밑에 붙여줍니다.

구조체나 클래스 내부에는 자동으로 inline으로 선언됩니다.

 

 

함수 포인터는 왜 사용할까요?

일종의 범용성을 위함입니다. 처음 배우는 사람들에게는 가르치지 않을 정도로 무시당하는 이론이지만,

라이브러리를 배포할 때에는 함수 포인터를 사용하는 것이 좋죠.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
 using namespace std;
 
 
 void Print1()
 {
     cout << "ㅋㅋ" << endl;
 }
 
 
 void Print2()
 {
     cout << "ㅎㅎ" << endl;
 }
 
 
 int main()
 {
     void (*func)();
 
     func = Print1;
     func();
 
     func = Print2;
     func();
 
     return 0;
 }


하지만, 인라인 키워드와 함수 포인터는 같이 사용할 수 없습니다.

인라인 함수는 컴파일 시간에 결정되기 때문이죠.

인라인으로 등록했지만 함수 포인터로 함수를 호출한다는 것은

인라인 키워드가 껍데기 역할을 해버리는 거죠..

 

 

여기까지가 C언어의 역사입니다.

C++에서는 이를 위해 함수객체를 제공하고 있습니다.

() 연산자를 오버로딩하여 객체를 함수처럼 사용하는 것이죠.

아까 말했죠? 구조체 안에 들어가면 자동 인라인입니다..

 

#include "stdafx.h"
#include <iostream>

using namespace std;


struct Func
{
    void operator() ()
    {
        cout << "kk" << endl;
    }
};


int main()
{
    Func f;
    f();

    return 0;
}


이제 여기서 모든 자료형으로 치환할 수 있는 템플릿만 적용하면

범용성은 완벽하게 해결됩니다.

이제 템플릿과 함수 객체를 왜 사용하는지 알아봤습니다.

 

그러나 여기서 문제가 하나 더 있습니다.

인라인이 꼭 좋은 것만은 아니죠.

함수를 코드로 치환해주는 원리이기 때문에 프로그램의 크기가 커질 수 밖에 없습니다.

 

그래서 나온게 람다(LAMBDA)라는 개념입니다.

이름없는 함수입니다.

C++11에서 새로 추가된 개념이지만 VS2010 부터 사용할 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
 
using namespace std;
 
// C++ 중급자
template <typename T>
struct SumFunctor
{
    T &value;
 
    SumFunctor(T &sum) : value(sum)
    {
    }
 
    void operator() (T &num)
    {
        value += num;
    }
};
 
 
int main()
{
    // 벡터와 크기
    vector<int> arr;
    int size;
 
    // 크기 입력받고 벡터 사이즈 예약
    cin >> size;
    arr.reserve(size);
 
    // 랜덤
    for(vector<int>::size_type i=0; i<arr.capacity(); i++)
    {
     arr.push_back(rand() % 100);
    }
 
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    // 벡터 합 : C++ 초보자
    int sum = 0;
 
    for(vector<int>::size_type i=0; i<arr.size(); i++)
    {
        sum += arr[i];
    }
 
    cout << sum << endl;
 
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    // 벡터 합 : C++ 중급자, 하지만 배보다 배꼽이 크다 ㅠㅠ
    sum = 0;
 
    // for each랑 헷갈리지 맙시다.
    for_each(arr.begin(), arr.end(), SumFunctor<int>(sum));
 
    cout << sum << endl;
 
 
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    // 벡터 합 : C++ 상급자
    sum = 0;
 
    // 람다는 런타임시 이름은 없지만,
    // 메모리 상에 임시적으로 존재하는 Closure Object가 생성됩니다.
    // 이것은 함수 객체처럼 동작하게 됩니다.
    for_each(arr.begin(), arr.end(), [&sum](int i) { sum += i; } );
 
    cout << sum << endl;
 
 
    return 0;
}

코드 실행 결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
차례대로 초급, 중급, 상급 코드를 실행한 결과입니다.
맨 위에 입력받는 숫자는 랜덤 생성 개수이고,
그 아래 6줄은 각 시간과 합 입니다.
 
10
0.000001
497
0.000006
497
0.000005
497
 
100
0.000007
4572
0.000008
4572
0.000007
4572
 
1000
0.000071
48278
0.000068
48278
0.000028
48278
 
10000
0.000703
499740
0.000247
499740
0.000241
499740
 
100000
0.007050
4945686
0.002403
4945686
0.002381
4945686


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://scor7910.tistory.com/55



Visual C++ 팀블로그에 C++0x에 대한 소개 자료중 람다(Lambda)에 관한 내용을 번역했습니다. 
차후에 나머지도 번역해서 올리겠습니다. 
번역이 만만치 않은 작업이근영....

 원본 : 

http://blogs.msdn.com/vcblog/archive/2008/10/28/lambdas-auto-and-static-assert-c-0x-features-in-vc10-part-1.aspx

 

Lambdas, auto, and static_assert: C++0x Features in VC10, Part 1

마이크로소프트 비절 스투디오 2010CTP(Community Technology Preview)에서는 C++ 0x 포함된 lambdasautostatic_assertrvalue references  라는 4가지의 개념을 제공합니다.  여기서는 처음 세가지에 대해이야기를 하겠습니다.

첫번째로 내용을 쓰기전..:

 

1.  포스트는 Visual C++ 라이브러리 개발자인Stephan T. Lavavej 님이작성했습니다그리고 Stephan T. Lavavej님이 위의 네가지 기능에 대한구현을 담당하지 않았음을 밝힘니다.

 

2. 내용에서 VS 2010에서 Visual C++ 컴파일러를 VC10이라고 칭할 입니다(10 2010 약어가 아님).

 

3. C++0x 아직 논의 중인 차세대 C++ 표준을 의미합니다.

(표준 위원회(The Standardization Committee) 2009 C++09라는이름으로 발표되기 원하지만 2010이나  걸릴수 있기 때문에 x 붙였다는조크도 있다네요. C++98  C++03  현재 C++ 표준을 의미 합니다.(여기서 역사이야기는 하지 않고 2003년에 발표된 C++표준은 1998년에발표된 C++ 대한 단순한 서비스팩” 이었고 대부분의 사람들은 이들의차이점에 대해 무시하였죠.  C++03  C++0x  완전히 다릅니다.

 

4.  나는 C++ 0x 완벽하고 멋지게 구성하고 있을 표준 위원회(The Standardization Committee) 감사를 표합니다그들은 또한 아래의 링크에좋은 자료를 올려 두었습니다 

C++0x language feature status: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html

C++0x library feature status: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html

C++0x Working Draft: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf

 

5.  어디에나 버그는 있습니다. (많지 않기를 원하지만..), 그게  CTP특성입니다버그가 발견되면 마이크로 소프트 커넥트 리포팅 해주세요.

 

이제 본론으로 들어가겠습니다.

 

 

 

 

 

lambdas

C++0x에는 명명되지않은(unnamed) 함수객체(function objects)수동으로 선언과 정의를 하지않고이것을 포함하고 있는  함수객체(function objects) 사용할  있는 람다(lambda) 수식 있습니다.

아래는 람다를 사용한 "Hello, World" 예제 입니다. :

 

C:\Temp>type meow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 meow.cpp > NUL && meow

0 1 2 3 4 5 6 7 8 9

 

 

 

 []  람다-소개자 (lambda-introducer) 입니다컴파일러에게 람다 수식이시작했다는 것을 알려주는 역할을 합니다(int n) 람다-매개변수-선언(lambda-parameter-declaration) 입니다어떤 명명되지 않은(unnamed) 함수 객체 클래스의 연산자( ()연산자를 의미하는듯) 실행이 되어지는지컴파일러에게 알려주는 역할을 합니다마지막으로  { cout << n << " "; }   복합-서술(compound-statement)부분이고 , 명명되지 않은 함수 객체의몸체(정의부입니다기본적으로 명명되지 않은 함수 객체의 연산자는 void리턴합니다.

 

 

그러면 C++0x에서 쓰여진 람다를 현재 C++ 구현한다면 어떻게 구현되는지보겠습니다.

 

C:\Temp>type meow98.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

struct LambdaFunctor {

    void operator()(int n) const {

        cout << n << " ";

    }

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), LambdaFunctor());

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 meow98.cpp > NUL && meow98

0 1 2 3 4 5 6 7 8 9

 

 

 

이제부터 명명되지않은 함수 객체 클래스의 () 연산자는 void 리턴한다” 람다는 void 리턴한다라고 말하겠습니다.  하지만람다 수식이 클래스를정의하고 생성하는 것은 중요하니 기억해 두세요.

 

물론람다의 복합-서술(compound-statement)구문은  여러줄로  있습니다.

 

C:\Temp>type multimeow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), [](int n) {

        cout << n;

 

        if (n % 2 == 0) {

            cout << " even ";

        } else {

            cout << " odd ";

        }

    });

 

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 multimeow.cpp > NUL && multimeow

0 even 1 odd 2 even 3 odd 4 even 5 odd 6 even 7 odd 8 even 9 odd

 

 

 

그리고람다는 항상 void 리턴 하지 않습니다만약 람다의 복합-서술compound-statement) { return expression; } 되어 있다면람다의리턴 타입은 자동으로 수식의 타입으로 만들어 줍니다.

C:\Temp>type cubicmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    deque<int> d;

 

    transform(v.begin(), v.end(), front_inserter(d), [](int n) { return n * n * n; });

 

    for_each(d.begin(), d.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 cubicmeow.cpp > NUL && cubicmeow

729 512 343 216 125 64 27 8 1 0

 

 

 

여기서 n * n * n  int 타입이고 람다의 함수기 호출하 ()연산자는 int리턴합니다.

 

람다로  복작한 복합-서술(compound-statements)구문은  자동으로리턴타입을 만들어   없습니다.  아래 코드와 같이 리턴타입을 명시해주어야 합니다


C:\Temp>type returnmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    deque<double> d;

 

    transform(v.begin(), v.end(), front_inserter(d), [](int n) -> double {

        if (n % 2 == 0) {

            return n * n * n;

        } else {

            return n / 2.0;

        }

    });

 

    for_each(d.begin(), d.end(), [](double x) { cout << x << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 returnmeow.cpp > NUL && returnmeow

4.5 512 3.5 216 2.5 64 1.5 8 0.5 0

 

 







































“-> double”  부가적으로 사용할  있는 람다-리턴-타입-구문 (lambda-return-type-clause )입니다.  많은 개발자들이  리턴 타입이왼쪽에 오지 않는 건지 궁금해 하겠지만,  lambda-introducer ( [] ) 앞에있지 않으면 컴파일러는 람다 문법의 시작을 알수 없기 때문입니다.

 

만약 람다 리턴 타입 구문을 쓰지 않으면 다음과 같은 컴파일 에러가발생됩니다

C:\Temp>cl /EHsc /nologo /W4 borkedreturnmeow.cpp

borkedreturnmeow.cpp

borkedreturnmeow.cpp(20) : error C3499: a lambda that has been specified to have a void return type cannot return a value

borkedreturnmeow.cpp(22) : error C3499: a lambda that has been specified to have a void return type cannot return a value

 

 

지금까지 보여준 람다는 데이터 멤버가 없는(stateless) 것들입니다.

여러분들은 지역변수를 캡쳐링(capturing)”해서 데이터 멤버를 가지고 있는람다를 만들  있습니다.   비어있는 람다 소개자lambda-introducer) [] 데이터 멤버가 없는 람다 라는 것을 의미 하고캡쳐리스트(capture-list)지정하여 데이터 멤버를 가지는 람다를 만들  있습니다.

 

C:\Temp>type capturekittybyvalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 0;

    int y = 0;

 

    // op>>() 입력 스트림에 개행 문자를  남겨야 하는데,

    // 이건  귀찮으므로 쓰지 않을 것을 추천합니다.

    // 대신  라인을 읽거나 읽어서 파싱하는 루틴을 만들고

    // 싶다면getline(cin,str)함수를 사용하세요.

    // 여기서는 간단하게 쓰기 위해 op>>() 썼습니다.

 

 

    cout << "Input: ";

    cin >> x >> y;

 

    v.erase(remove_if(v.begin(), v.end(), [x, y](int n) { return x < n && n < y; }), v.end());

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyvalue.cpp > NUL && capturekittybyvalue

Input: 4 7

0 1 2 3 4 7 8 9

 

 

캡쳐 리스트를 정의하지 않으면 아래의 오류 코드가 발생됩니다

C:\Temp>cl /EHsc /nologo /W4 borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp(27) : error C3493: 'x' cannot be implicitly captured as no default capture mode has been specified

borkedcapturekittybyvalue.cpp(27) : error C3493: 'y' cannot be implicitly captured as no default capture mode has been specified

 

 

(기본(default) 캡쳐에 대한 설명은 나중에…)

 

람다 수식은 기본적으로 명명되지 않은 함수 객체 클래스를 정의 한다는 것을기억해두시길 바랍니다.  복합-서술(compound-statement) 구문인 { return x < n && n < y; } 클래스의 ()연산자 함수 몸체에 해당됩니다.

어휘상 복합-서술(compound-statement) 구문이 어휘상 main() 함수 내부에있지만 개념상 main()함수 외부에 존재 하는  입니다.  그래서 main() 함수내부에 있는 지역변수를 바로 사용할  없고 람다 내부에서 캡쳐하여 사용해야합니다.

 

아래 예제는 위의람다 캡쳐 예제를 현재 C++ 표준으로 구현한  입니다.

 

C:\Temp>type capturekittybyvalue98.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

class LambdaFunctor {

public:

    LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }

 

    bool operator()(int n) const { return m_a < n && n < m_b; }

 

private:

    int m_a;

    int m_b;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 0;

    int y = 0;

 

    cout << "Input: ";

    cin >> x >> y; // EVIL! <<- 이러면 안된다는 말입니다..

 

    v.erase(remove_if(v.begin(), v.end(), LambdaFunctor(x, y)), v.end());

 

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyvalue98.cpp > NUL && capturekittybyvalue98

Input: 4 7

0 1 2 3 4 7 8 9

 

 

 

여기서,  캡쳐의 의미가 값에 의한(전달)” 이라는 것이 명확하게  있습니다지역변수의 복사본이 함수객체 내부 함수에 저장 되는 것을  있습니다.

이는 함수 객체가 캡쳐하기 위해 생성된 지역변수 보다  오래 남을  있게합니다.

아래 사항들을 알아 두세요.

(a) 함수호출 연산자( ()연산자 ) 기본적으로 const 이기 때문에캡쳐된복사본은 람다 내부에서 수정될  없습니다.

(b) 어떤 객체는 복사하기 비용이 많이 듭니다.  

(c) 지역변수를 변경하면 캡쳐된 복사본에는 아무런 영향을 없습니다. (값에의한 전달에 관한 내용).

 

 부분은 다음에 필요할  이야기 하겠습니다 

 

캡쳐하고 싶은 지역변수들을 모두 지정하는  대신 값에 의한 복사로  모두캡쳐”   있습니다.  이것을 가능하게 하는 문법이 람다 소개자(lambda-introducer)  “ [=] “ 입니다. ( capture-default  =’  여러분이대입연산자나 복사 초기화 Foo foo = bar  생각하게 하기 위함입니다;

사실 복사는 위의 예제에서 m_a(a) 같이 한번에 초기화 하기 위해 만들어졌습니다.

 

C:\Temp>type defaultcapturekittybyvalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 0;

    int y = 0;

 

    cout << "Input: ";

    cin >> x >> y; // EVIL!

 

    v.erase(remove_if(v.begin(), v.end(), [=](int n) { return x < n && n < y; }), v.end());

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 defaultcapturekittybyvalue.cpp > NUL && defaultcapturekittybyvalue

Input: 4 7

0 1 2 3 4 7 8 9

 

 

 

컴파일러는 람다 구문 내에 있는 x y 보고, main()함수에 있는 x y값을 캡쳐 합니다.

위에서 (a)항목에서 말한 람다의 함수 호출 연산자( () 연산자 기본적으로 const이기 때문에 캡쳐한 복사본(원본) 수정할  없지만,  람다 ()연산자함수 내부에서 mutable(변하기 쉬운키워드를 사용하여 non-const 만들면됩니다.

 

C:\Temp>type capturekittybymutablevalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), [=](int& r) mutable {

        const int old = r;

 

        r *= x * y;

 

        x = y;

        y = old;

    });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << x << ", " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybymutablevalue.cpp > NUL && capturekittybymutablevalue

0 0 0 6 24 60 120 210 336 504

1, 1

 

 

v 있는  값에 이전의 x,y곱을 곱하는 동작을 합니다.

 (이전의 모든 요소들과 곱해주도록 partial_sum() 함수나이전의 요소들과바로 곱해주도록 adjacent_difference() 함수를 이용해서 구현할수  없어서예제처럼 구현이 되어있습니다.)  위에서 말한 항목 “(d) 캡쳐된 복사본을원본의 지역변수에 적용되지 않는다”  기억하세요.

 

위에 (b),(C),(d) 약속을 우회할 방법을 알고 싶다면복사를 하지 않고 람다내부에서 값을 변경하는 것을 관찰하는 방법람다함수 내부에서 값을 바꿀 있지 않을까요이럴 경우 참조(reference) 값을 캡쳐하는 방법을 생각 했을겁니다.  이렇게 하는 방법은 람다-소개자(lambda introducer) [&x, &y]하면 됩니다( [&x, &y]  X& x, Y& y 생각하세요포인터의 전달이 아닌참조(Reference)입니다.) :

 

C:\Temp>type capturekittybyreference.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), [&x, &y](int& r) {

        const int old = r;

 

        r *= x * y;

 

        x = y;

        y = old;

    });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << x << ", " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyreference.cpp > NUL && capturekittybyreference

0 0 0 6 24 60 120 210 336 504

8, 9

 

 

위의 예제 capturekittybymutablevalue.cpp  다른 점은

(1)람다-소개자 모양lambda-introducer) [&x, &y]

(2) mutable 키워드가 없습니다.

(3) main()함수 지역 변수 x,y 값이 람다함수 내부에서 변경된 것이 main()함수에서도 적용되었습니다.

 세가지 입니다.

 

 

위의 코드를 현재 C++ 구현 한다면 아래와 같이 구현할  있습니다.:

C:\Temp>type capturekittybyreference98.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

#pragma warning(push)

#pragma warning(disable: 4512) // assignment operator could not be generated

 

class LambdaFunctor {

public:

    LambdaFunctor(int& a, int& b) : m_a(a), m_b(b) { }

 

    void operator()(int& r) const {

        const int old = r;

 

        r *= m_a * m_b;

 

        m_a = m_b;

        m_b = old;

    }

 

private:

    int& m_a;

    int& m_b;

};

 

#pragma warning(pop)

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), LambdaFunctor(x, y));

 

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

    cout << endl;

 

    cout << x << ", " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyreference98.cpp > NUL && capturekittybyreference98

0 0 0 6 24 60 120 210 336 504

8, 9

 

 

 

(람다를 사용하면 , 컴파일러는 자동으로 C4512 경고를 꺼줍니다.)

  

지역 변수를 참조로 캡쳐하면함수 객체는 참조(reference) 자신의참조(reference) 멤버변수에 저장합니다.

 이렇게 하면 함수 객체 () 함수에서 변경된 값이 적용이 되는 겁니다.

(함수 객체의 ()함수  const 것에 주의하세요우리는(VC++ 컴파일러팀) mutable이라 하지 않습니다. const 붙인 것은 단순히  함수객체의 멤버데이터의 변경을 막기 위함 입니다멤버데이터들은 참조하는   변경할수없고 멤버 데이터들이 참조하는 값을 변경할  있습니다. Const 아닌 함수객체는 얕은 복사입니다.)

 

물론만약 람다함수 객체가 참조로 캡쳐된 지역변수들 보다 오래생존(instantiate) 하게 되면 프로그램은 죽게 됩니다(crashtrocity :  뭔소리야?!!).

 

또한기본 캡쳐를 사용할 수도 있습니다. ;  [&]  참조로 모든 변수를캡쳐한다 의미 합니다.

 

만약  개는 참조로하고  개는 값으로 캡쳐하고 싶을땐 어떻게 할까요?

[a, b, c, &d, e, &f, g] 방법을 생각하겠지만여러분들은 capture-default  지정하고 특정 지역변수들에 대해 오버라이드   있습니다.

아래 예제는 위에 나온 예제capturekittybymutablevalue.cpp  수정한 입니다.:

  

C:\Temp>type overridekitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int sum = 0;

    int product = 1;

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), [=, &sum, &product](int& r) mutable {

        sum += r;

 

        if (r != 0) {

            product *= r;

        }

 

        const int old = r;

 

        r *= x * y;

 

        x = y;

        y = old;

    });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << "sum: " << sum << ", product: " << product << endl;

    cout << "x: " << x << ", y: " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 overridekitty.cpp && overridekitty

overridekitty.cpp

0 0 0 6 24 60 120 210 336 504

sum: 45, product: 362880

x: 1, y: 1

 

 

여기서는 x,y 값으로 캡쳐하고, (람다 내부에서만 수정되어져야 하기 때문) sum, produce 참조로 캡쳐 했습니다반대로 lambda-introducer  [&, x, y]  해도 같은 같은 결과 입니다.

 

그럼 this 는 어떻게 할 까요?

C:\Temp>type memberkitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

class Kitty {

public:

    explicit Kitty(int toys) : m_toys(toys) { }

 

    void meow(const vector<int>& v) const {

        for_each(v.begin(), v.end(), [m_toys](int n) {

            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;

        });

    }

 

private:

    int m_toys;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 3; ++i) {

        v.push_back(i);

    }

 

    Kitty k(5);

    k.meow(v);

}

 

C:\Temp>cl /EHsc /nologo /W4 memberkitty.cpp

memberkitty.cpp

memberkitty.cpp(12) : error C3480: 'Kitty::m_toys': a lambda capture variable must be from an enclosing function scope

 

 

  

람다 수식 문법은 지역변수를 캡쳐하는건 허용하지만 멤버 변수의 캡쳐는허용하지 않습니다.  대신 특별히 this 포인터를 캡쳐   있습니다. :

C:\Temp>type workingmemberkitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

class Kitty {

public:

    explicit Kitty(int toys) : m_toys(toys) { }

 

    void meow(const vector<int>& v) const {

        for_each(v.begin(), v.end(), [this](int n) {

            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;

        });

    }

 

private:

    int m_toys;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 3; ++i) {

        v.push_back(i);

    }

 

    Kitty k(5);

    k.meow(v);

}

 

C:\Temp>cl /EHsc /nologo /W4 workingmemberkitty.cpp > NUL && workingmemberkitty

If you gave me 0 toys, I would have 5 toys total.

If you gave me 1 toys, I would have 6 toys total.

If you gave me 2 toys, I would have 7 toys total.

 

 

this 캡쳐하면 m_toys멤버 변수는 암시적으로 this->m_toys 의미한다는 것을 생각   있습니다.  (람다 수식 내부에선, this 캡쳐된 this 의미하지 람다 객체의 this포인터를 의미하지 않습니다. : 람다함수의 this 포인터에는 접근할  없습니다.)

 

this 암시적으로 캡쳐할  있습니다:

C:\Temp>type implicitmemberkitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

class Kitty {

public:

    explicit Kitty(int toys) : m_toys(toys) { }

 

    void meow(const vector<int>& v) const {

        for_each(v.begin(), v.end(), [=](int n) {

            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;

        });

    }

 

private:

    int m_toys;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 3; ++i) {

        v.push_back(i);

    }

 

    Kitty k(5);

    k.meow(v);

}

 

C:\Temp>cl /EHsc /nologo /W4 implicitmemberkitty.cpp > NUL && implicitmemberkitty

If you gave me 0 toys, I would have 5 toys total.

If you gave me 1 toys, I would have 6 toys total.

If you gave me 2 toys, I would have 7 toys total.

 

 

 

[&] 할수 있지만 ,  this 포함 되지 않습니다.(항상 값으로 전달됩니다.) , [&this] 안됩니다.

 

람다 소개자(lambda-introducer) 아무런 값을 넣고 싶지 않으면, lambda-parameter-declaration 전체는 생략 됩니다. :

C:\Temp>type nullarykitty.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    int i = 0;

 

    generate_n(back_inserter(v), 10, [&] { return i++; });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << "i: " << i << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 nullarykitty.cpp > NUL && nullarykitty

0 1 2 3 4 5 6 7 8 9

i: 10

 

 

 

 [&]() { return i++; } 비교해서 2문자( () )  빠져 있습니다 . 

lambda-parameter-declaration  생략하는  여러분들 마음입니다.

 

 

장난 삼아 아래의 코드는 C++0x 에서 유효한 코드입니다.:

C:\Temp>type nokitty.cpp

int main() {

    [](){}();

    []{}();

}

 

 

 

위의 예제는 아무런 동작을 하지 않는  개의 람다를 생성합니다.

( 번째는 lambda-parameter-declaration 있고  번째는 없습니다.)

 

추가적으로 사용할  있는 lambda-parameter-declaration  문법적으로아래와 같이 구성되어있습니다. :

 

( lambda-parameter-declaration-listopt ) mutableopt exception-specificationopt lambda-return-type-clauseopt

 

그래서 mutable 이나 ->리턴타입  지정하고 싶으면 lambsa-introducer( [] )  사이에  공간이 필요 합니다.

 

마지막으로람다는 보통의 함수 객체를 만들어 내기 때문에 , 함수 객체들을 tr1::function  보관  수도 있습니다.:

C:\Temp>type tr1kitty.cpp

#include <algorithm>

#include <functional>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

using namespace std::tr1;

 

void meow(const vector<int>& v, const function<void (int)>& f) {

    for_each(v.begin(), v.end(), f);

    cout << endl;

}

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    meow(v, [](int n) { cout << n << " "; });

    meow(v, [](int n) { cout << n * n << " "; });

 

    function<void (int)> g = [](int n) { cout << n * n * n << " "; };

 

    meow(v, g);

}

 

C:\Temp>cl /EHsc /nologo /W4 tr1kitty.cpp > NUL && tr1kitty

0 1 2 3 4 5 6 7 8 9

0 1 4 9 16 25 36 49 64 81

0 1 8 27 64 125 216 343 512 729

 

 

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://blog.omniavinco.kr/entry/C-%EB%9E%8C%EB%8B%A4-functional-%ED%97%A4%EB%8D%94


C++0x 표준에 포함되어 있으며 지원 되는 컴파일러 버전은 다음과 같다.

  • GCC : 4.5 이상
  • MSVC(Visual Studio) : 10.0 이상(VS 2010이상)
  • Clang : 3.1이상
  • EDG eccp : 4.1
  • Intel C++ : 11.0


Lambda syntax

[/*hook*/](/*arguments*/)->/*return_type*/{/*code*/}

위에 주석으로 써져있는 부분에 적합한 내용을 적어주면 된다.

  • hook : lambda 외부의 변수를 lambda 속에서 capture 해 사용할 수 있게 한다. 레퍼런스나 값 복사로 capture할 수 있다. &만 쓰면 모든 변수를 레퍼런스로 capture한다.
  • arguments : lambda의 인자. 함수의 인자와 똑같은 방식으로 표기하면 된다.
  • return_type : lambda의 return value의 type.
  • code : lambda 내용, C++ syntax에 맞게 쓰면 된다.

위와 같은 syntax로 lambda를 선언할 수 있다.

lambda를 실행하는 방법은 함수호출과 다를바가 없다.

// 변수에 저장하고 호출하는 방법
auto exampleLambda = [&](void)->void{std::cout << "Hello, Lambda";};
exampleLambda();
 
// 이렇게 하면 생성하자마자 바로 호출한다.
[&](void)->void{std::cout << "Hello, Lambda";}();
 
// capture는 이렇게...
int n = 0;
[&n](int a)->void{n = a;}();
// 하면 다음과 같은 함수가 된다.
void someFunction(int a)
{
    n = a;
}
 
// 인자가 여러개일 때는 함수와 다를 바 없이 쓰면 된다.
[](int a, int b)->bool{return a > b;};

위와 같이 하면 된다.
물론 예제에서는 모두 코드가 한줄이어서 한줄로 썼지만 코드는 그냥 내려서 써도 된다.


lambda의 타입

auto타입을 쓰면 lambda가 변수로 저장은 되지만 이러면 함수의 인자나, 객체의 변수로 활용할 수가 없다.
functional 헤더의 std::function template 타입을 사용하면 해당 함수의 타입을 선언할 수 있다.

std::function</*return_type*/(/*arguments*/)>
 
std::function<bool(int, int)> compareFunction= [](int a, int b)->bool{return a < b;};
std::function<int(int)> doubler = [](int a)->int{return a * 2;};

위와 같이 선언이 가능하며 return_type과 arguments는 앞서 설명한 lambda와 동일하다.

bind를 사용하면 lambda의 인자에 미리 값을 설정해놓을 수 있다.
즉, add라는 두 값을 더한 결과를 돌려주는 lambda가 있으면
bind를 사용해서 addOne이라는 1을 더한 결과를 돌려주는 lambda로 만들 수 있다.
boost에서의 bind는 인자 갯수와 상관없이 가능하다고 한다.
bind 예제는... 패스 =ㅅ=;;;


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://specialmylife.tistory.com/entry/%EB%9E%8C%EB%8B%A4



에러가 나지 않지만 출력은 되지 않는다.
[] {
};
[] {std::cout << "Hello World<< std::endl; };

마지막에 ()를 붙여 함수를 호출한다.
[] { std::cout << "Hello World<< std::endl; }();

//auto로 받아 호출할 수 있다.
auto f = [] { std::cout << "Hello World 1" << std::endl;};
f();

//템플릿 함수의 자로 사용되어 호출될 수 있다.
template<typename T>
void templateFunc(T t)
{
t();
}

templateFunc(f);

//매개 변수 사용
auto f= [](int n) { std::cout << "value  " << n << std::endl; };
f1(3);
f1(6);
f1(9);

//리턴 타입
auto rf= [] {return 3.14f; };
auto rf2 = [](float f) {return f; };
auto rf3 []()->float {return 3.14f; };

float float1 = rf1();
float float2 = rf2(3.14f);
float float3 = rf3();

std::cout << float1 << " " << float2 << " " << float3 << " " << std::endl;

// stl에서 람다 사용
std::vector<int> vector1;
vector1.push_back(10);
vector1.push_back(20);
vector1.push_back(30);
std::for_each(vector1.begin(), vector1.end(), [](int n) {std::cout << n << std::endl; });


//리턴형이 람다인 함수 사용
std::function< void() > ff()
{
std::string str("lambda");
return [=] { std::cout << "Hello " << str.c_str() << std::endl; };
}
auto func = ff();
func();
ff()();


// 람다 캡션 - 람다 함수의 몸통 밖에 있는 변수 접근
// & 레퍼런스
int x = 100;
[&]() { std::cout << x << std::endl; x = 200; }();
std::cout << x << std::endl;

// = 복사
[=]() { std::cout << x << std::endl; }();
[=]() { std::cout << x << std::endl; x = 300; }();  Error
[=]() mutable { std::cout << x << std::endl; x = 300; }();

//default
int n1, n2, n3, n4, n5;
[]() { n1 = 10; }();   Error
[&, n1, n2] {}; //n3, n4, n5 참조, n1, n2 복사
[=, &n1, &n2] {}; //n3, n4, n5 복사, n1, n2 참조
[&n1, &n2] {n3 = 10; }; //Error
[n1, n1] {};     //Error 같은 변수
[&, &n1] {};    //Error n1 default 이미 사용


//클래스안에 람다함수 - firend
class Clambda
{
public:
int m_public = 10;
int publicFunc() { return m_public; }
private:
int m_private = 20;
int privateFunc() { return m_private; }

public:
Clambda() {}
std::function< void() > m_function()
{
return [this]() { m_public = 20; m_private = 30; };
}

void print() 
{
std::cout << m_public << " " << m_private << std::endl;
}
};

Clambda lambda;
lambda.m_function()();
lambda.print();

// 제너릭처럼 사용
auto sum = [](auto a, auto  b) { return a + b; };
int i = sum(3, 6);
double d = sum(3.14, 2.77);

// 캡쳐하지 않은 람다는 함수 포인터로 형 변환 가능
auto L = [](auto a, auto b) { return a + b; };
int(*pf)(int, int) = L;
int p = (*pf)(1, 2);

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://egloos.zum.com/sweeper/v/3015050


5. lambda in class

클래스의 멤버 함수 내에 lambda 함수를 정의하고, 이 lambda 함수에서 해당 클래스의 멤버를 호출하는 것이 가능하다.

클래스 멤버 함수 내 lambda 함수는 해당 클래스에서 friend 함수로 인식하므로,
lambda 함수에서 클래스의 private 멤버에도 접근이 가능하다.

클래스의 멤버를 호출할 때는 반드시 ‘this’ 를 캡쳐해야 한다.

  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4.  
  5. using namespace std;
  6.  
  7. class PrimeNumber
  8. {
  9. public:
  10.     PrimeNumber()
  11.     {
  12.         m_primeList.push_back(1);
  13.         m_primeList.push_back(2);
  14.         m_primeList.push_back(5);
  15.         m_primeList.push_back(7);
  16.  
  17.         // ...
  18.     }
  19.  
  20.     void PrintPrimeNumbers() const
  21.     {
  22.         // this를 캡쳐한 것에 주목!!!
  23.         for_each(m_primeList.begin(), m_primeList.end()[this] (int primeNumber)
  24.         {
  25.             _Print(primeNumber);
  26.         });
  27.     }
  28.  
  29. private:
  30.     typedef vector<int> PrimeNumberList;
  31.     PrimeNumberList m_primeList;
  32.  
  33.     void _Print(int primeNumber) const
  34.     {
  35.         cout << "The prime number : " << primeNumber << endl;
  36.     }
  37. };
  38.  
  39. int _tmain(int argc, _TCHAR* argv[])
  40. {
  41.     PrimeNumber pn;
  42.     pn.PrintPrimeNumbers();
  43.  
  44.     return 0;
  45. }

위 예제의 lambda 함수에서 보듯이 같이 [] 캡쳐 영역에 반드시 this 를 기술하여야 하며, 
PrimeNumber 클래스의 private 멤버 함수인 _Print 함수를 호출하고 있다.

그리고, 당연히 lambda 함수를 통해 멤버 변수에도 접근이 가능하다.


6. Recursive lambda

lamba 함수 역시 함수의 특성을 모두 가지고 있으므로, 재귀(recursive)가 가능하다.

  1. #include <functional>
  2.  
  3. using namespace std;
  4. using namespace std::tr1;
  5.  
  6. int _tmain(int argc, _TCHAR* argv[])
  7. {
  8.     // function을 사용하였음에 주의하라
  9.     function<int (int)> Factorial = [&Factorial] (int num) -> int
  10.     {
  11.         return num <= 1 ? 1 : num * Factorial(num - 1);
  12.     };
  13.  
  14.     // 5 * 4 * 3 * 2 * 1 = 120
  15.     int fact5 = Factorial(5);
  16.  
  17.     return 0;
  18. }

흔하디 흔한 팩토리얼 코드이지만, 위 예제에서 중요한 포인트가 하나 숨어 있다.

보통 lambda 함수를 대입시킬 변수의 타입을 tr1::function이 아닌 auto로 잡는다.
헌데, lambda 함수를 recursive 함수로 작성할 땐 반드시 auto가 아닌 tr1::function 타입으로 잡아야 한다.

그렇지 않으면, 아래 예제와 같은 컴파일 에러가 발생한다.

  1. // 1. error C3536: 'Factorial': 초기화되기 전에 사용할 수 없습니다.
  2. // 2. error C3533: 'auto &': 매개 변수에 'auto'가 포함된 형식을 포함할 수 없습니다.
  3. // 3. error C3531: 'Factorial': 형식에 'auto'가 포함된 기호에는 이니셜라이저가 있어야 합니다.
  4. // 4. error C2064: 항은 1개의 인수를 받아들이는 함수로 계산되지 않습니다.
  5.  
  6. auto Factorial = [&Factorial] (int num) -> int
  7. {
  8.     return num <= 1 ? 1 : num * Factorial(num - 1);
  9. };

auto는 타입 추론을 하는 녀석이다.
위 함수에서 auto가 허용되지 않는 것은 아직 타입이 제대로 추론도 되기 전에 재귀 함수를 사용하려 하니 문제가 되는 것이다.

다시 얘기하지만, lambda recursive 함수의 대입 타입으로 auto를 사용하면 안 된다.
제대로 된 std::tr1::function 타입을 사용할 것.


7. lambda와 closure

간혹 lambda가 clousure라고 혼동하는 사람들이 꽤 있다.

lambda는 closure가 아니다.
단지 lambda를 이용하여 closure의 특성을 구현해 낼 수 있는 것이다.

아래 예제를 한 번 살펴 보자.
아래 예제의 두 번째 f 호출의 결과는 38인가? 70인가?

  1. #include <iostream>
  2. #include <functional>
  3.  
  4. using namespace std;
  5. using namespace std::tr1;
  6.  
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9.     int a = 7, b = 3;
  10.  
  11.     // 외부 변수 a와 b를 복사 캡쳐하고, int x를 파라미터로 받는 lambda 함수
  12.     // a * x + b 의 결과를 출력한다.
  13.     auto closureFunc = [a, b] (int x)
  14.     {
  15.         cout << a * x + b << endl;
  16.     };
  17.  
  18.     closureFunc(5);  // 7 * 5  + 3 = 38 를 출력
  19.  
  20.     // 여기에서 a와 b를 각각 7 -> 10, 3 -> 20으로 바꾸었다
  21.     a = 10, b = 20;
  22.  
  23.     // 그렇다면 지금의 closureFunc(5)는
  24.     // 7 * 5 + 3 = 38 일까? 아니면 10 * 5 + 20 = 70일까?
  25.     closureFunc(5);
  26.  
  27.     return 0;
  28. }

두 번째 closureFunc 함수 호출의 결과는 38이 출력된다.

function<void (int)> closureFunc 는 이미 생성될 때 a와 b가 7과 3 으로 바인딩 되었다.
따라서, closureFunc 는 이미 7 * 5 + 3 의 결과만을 출력하는 closure의 특성을 가지게 되었다.

다르게 표현하자면, closureFunc 함수는 a 와 b 의 값에 "Closed over(Closure)" 되었다고 할 수 있다. 
이와 갈은 변수의 바인딩 방식을 static(lexical) binding 이라 부른다.

Closure 특성이 유용한 이유는 위 예제에서도 볼 수 있듯이 late evaluation 이 가능하다는 것이다.

일단 특정 환경에서 함수를 정의한 후 나중에 함수를 호출하여 실행시킬 수 있는 것이다.
이 때 closure 함수는 바뀐 주변 환경과 관계없이 자신이 정의될 때의 환경에 따라 행동한다.

즉, closureFunc 함수는 언제 어디서 호출되더라도 a = 7, b = 3의 값을 보존하고 있는 것이다.

이러한 closure 의 특징은 하나의 환경 하에서 여러 개의 코드 블록을 정의하여, 
이후 해당 함수들이 필요할 때 실행시킬 수 있는 이른바 multiple function 의 사용을 가능하게 한다.


8. 정리

  1. lambda는 람다 표현식 또는 람다 함수, 그리고 이름 없는 함수라고 불리며, 함수 오브젝트 중 하나이다.
  2. lambda는 특별한 타입을 가지고 있다고 하지만 decltype 이나 sizeof 는 사용 불가
  3. lambda를 정의한 곳의 외부 변수를 lambda 내부에서 사용하고 싶을 때는 참조 또는 복사로 캡쳐한다.
  4. 클래스에서도 lambda를 사용할 수 있으며, 클래스는 lambda를 friend 함수로 인식한다.
  5. lamba는 closure가 아니다. 단지 lambda를 이용하여 closure의 특성을 구현할 수 있는 것이다.

지금까지 lambda에 대해 꽤나 긴 내용을 작성하며 살펴 보았다.

처음 lambda가 나왔을 때엔 그 가독성에 혀를 내 둘렀으나, 곰곰히 생각해보니 그건 가독성 문제가 아니라 익숙하지 않음이었다.
제대로 공부를 하고 점점 실무에 적용을 해 나가면서 익숙해지자 가독성 문제는 크게 느껴지지 않았다.

이를 보면서 느낀 점은 아는 만큼 보인다는 것이다.
업무가 과중하기에 공부의 절대량을 늘릴 순 없지만, 부지런히 갈고 닦아야 함은 진리이니라~

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: https://mrw0119.tistory.com/22



1. 람다식


람다식은 무명 메소드를 단순한 계산식으로 표현한 것 이다.


메소드는 크게 매개변수와 내부 식, 반환값으로 구성되어 있는데,


이들만 가지고 메소드를 계산식으로 표현한다.


무명 메소드에 비해 상당히 간결하게 표현되는 것을 확인할 수 있다.


람다식은 매개변수로 전해지는 a, b의 타입까지도 생략이 가능하다.


( 똑똑한 컴파일러가 좌항의 델리게이트의 타입을 참고해서 타입을 유추하는 것이다.  )


그럼 람다식에 대한 다음 예제를 작성해 보자.






2. 문 형식의 람다식


지금까지의 람다식은 단순한 계산식 하나만을 표현하였다.


그렇다면 람다식 내에서 메소드처럼 다양한 처리를 해줄 순 없을까? 


당연히 가능하다. 그냥 메소드처럼 중괄호 내에 작성하면 된다.



그냥 메소드를 간략화 했다고 보면 될 거 같다.


위의 내용을 프로그램으로 작성해서 확인해보자.





출처: https://mrw0119.tistory.com/22 [Game Developer W]


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



출처: https://hackersstudy.tistory.com/28



람다식은 delegate를 만드는데 사용할 수 있는 익명함수이다.


기존에 deleagate에서  람다식을 사용하면 간결하게 표현이 가능하다.


무슨말인지 잘 모르겠으니 코드를 보자


     

delegate double DelPlusMinus(int fVal, int mVal, int lVal);

 

public static void Main(string[] args)

{

    DelPlusMinus cal = delegate(int fVal, int mVal, int lVal)

    {

         return fVal + mVal + lVal;

     };

 

     Console.WriteLine(cal(5, 4, 1));

}



위의 코드는 delegate를 이용하여 3개의 변수를 더해서 출력하는 간단한 예제이다.


이것을 람다식으로 바꿔면 다음과 같은 코드가 된다.


delegate double DelPlusMinus(int fVal, int mVal, int lVal);

 

public static void Main(string[] args)

{

    //DelPlusMinus cal = delegate(int fVal, int mVal, int lVal)

    //{

    //    return fVal + mVal + lVal;

    //};

 

    DelPlusMinus cal = (int fVal, int mVal, int lVal) => { return fVal + mVal + lVal; };

 

    Console.WriteLine(cal(5, 4, 6));

}



위와 같이 여러줄에 걸쳐 작성해야하는 코드를 간단하게 작성이 가능하다.


다음코드를 한번 보자


delegate double DelPlusMinus(int fVal, int mVal, int lVal);

 

public static void Main(string[] args)

{

    //DelPlusMinus cal = (int fVal, int mVal, int lVal) => { return fVal + mVal + lVal; };

    DelPlusMinus cal = (fVal, mVal, lVal) => { return fVal + mVal + lVal; };

 

    Console.WriteLine(cal(5, 4, 6));

}


람다식은 위의 코드와 같이 변수 타입도 생략이 가능하여 더 간단한 표현이 가능하다.



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


















반응형


관련글 더보기

댓글 영역