[C++0x] Lambda Expressions and Closures | | 작성일 : 2008.11.23 | 작성자 : 아이오 교육센터 | www.ioacademy.co.kr | | | 글 순서 1. 기본개념 2. 람다 표현식(Lambda expressions)과 and 클로져(closure) 3. 람다 표현식과 리턴 타입 4. Capturing Local Variable 5. Nullary Lambda 6. Mutable lambda | | | 1. 기본 개념 | | STL의 알고리즘 중에는 함수 객체를 인자로 받는 것이 있습니다. for_each() 알고리즘이 대표적인 경우 입니다. 아래의 코드는 for_each()를 사용해서 주어진 구간의 모든 요소를 화면에 출력하는 코드 입니다. | | 01: #include <iostream>
02: #include <algorithm>
03: using namespace std;
04:
05: struct ShowFunctor
06: {
07: void operator()(int n) const
08: {
09: cout << n << endl;
10: }
11: };
12: int main()
13: {
14: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
15: for_each( x, x+10, ShowFunctor() );
16: } |
| | | 그런데 위 코드에 있는 ShowFunctor 같은 함수를 자주 만들어야 한다면 좀 번거로운 일입니다. C++0x는 ShowFunctor 와 같은 함수객체를 보다 쉽게 만들 수 있는 “람다 표현식(Lambda express)”라는 개념을 제공합니다. 아래 코드는 위 코드와 동일한 일을 수행합니다. 단, 완전한 함수객체를 만들어 사용하지 않고 람다 표현식을 사용하고 있습니다. | | 1: int main()
2: {
3: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
4: for_each( x, x+10, [](int n){ cout << n << endl;} );
5: } |
| * 위 예제를 포함한 이 글에 나오는 모든 예제들은 VS2010CTP 를 사용해서 test 되었습니다. | | 위 코드 중 다음의 표현이 람다 표현식 입니다. | [](int n){ cout << n << endl;} | 람다 표현식을 보고 컴파일러는 함수객체 클래스를 정의하고 객체를 생성하게 됩니다. 즉, 위 코드는 아래의 코드와 유사한 의미를 가지고 있습니다. | | 01: class closure_object
02: {
03: public:
04: void operator()(int n) const
05: {
06: cout << n << endl;
07: }
08: };
09: int main()
10: {
11: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
12: for_each( x, x+10, closure_object() );
13: } |
| | 람다 표현식에 의해 컴파일러가 생성한 함수객체는 클로져 객체(Closure Object) 라고 부릅니다.클로져 객체는 함수호출연산자, 생성자, data멤버를 가진 함수객체와 유사하게 동작합니다. | | 람다 표현식을 간단히 살펴 보도록 하겠습니다.. | | - [] 는 “lambda introducer” 라고 부르는데, 람다 표현식이 시작됨을 나타냅니다. - (int n) 은 “람다 파라미터 선언(lambda parameter declaration)” 이라고 부르는데, 클로져 객체의 함수호출 연산자(즉, ()연산자)가 가져야 하는 파라미터 목록을 컴파일러에게 알려주는 것입니다. - 마지막으로 “{ cout << n << endl;}” 는 클로져 객체의 함수 호출연산자의 구현부를 정의 하는 것입니다. - 디폴트로 람다 표현식의 리턴 타입은 void를 리턴 합니다. - 여러 개의 람다 표현식은 각각 자신만의 타입을 가지게 됩니다.
| | 물론, 람다 표현식을 사용하지 않고 완전한 함수객체를 만들어 사용하면 되지만 람다 표현식을 사용하는 것이 좀더 편리 합니다. | | 2. Lambda Expression and Closure | | | [] 를 사용해서 익명의 함수객체를 명시하는 표현 식을 “람다 표현식(lambda expression)” 또는 “람다 함수(lambda function)” 라고 합니다. 또한, 람다표현식의 결과로 컴파일러에 의해 자동으로 생성된 익명의 함수객체(unnamed function object) 를 “클로져(Closure)” 라고 합니다. | | 람다 표현식의 구현은 여러 줄로 구성될 수 도 있습니다. | | | 01: #include <iostream>
02: #include <algorithm>
03: using namespace std;
04:
05: int main()
06: {
07: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
08:
09: for_each( x, x + 10, [](int n ) {
10: if ( n % 2 == 0 )
11: cout << "even ";
12: else
13: cout << "odd ";
14: });
15:
16: cout << endl;
17: } |
| | | 3. Lambda Expression and return type | | 람다 표현식은 리턴값은 가질수도 있습니다. 아래의 예제를 보겠습니다 | | 01: #include <iostream>
02: #include <algorithm>
03: #include <vector>
04: using namespace std;
05:
06: int main()
07: {
08: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
09: vector<double> v;
10:
11: transform( x, x+10, back_inserter(v), [](int n) -> double {
12: if ( n % 2 == 0 ) return n;
13: return n / 2.0;
14: });
15:
16: for_each( v.begin(), v.end(), [](double n) { cout << n << " ";});
17: cout << endl;
18: } |
| | “->double” 은 “lambda-return-type-clause” 라고 부르는데, 람다 표현식에서 리턴 타입을 지정하는 구문입니다. | | 아래와 같은 경우, 리턴 타입을 지정하는 것은 생략할 수 있습니다. | | 1. 람다 표현식이 리턴하는 값이 없을 경우 즉, “->void”는 생략이 가능합니다. | 2. 람다 표현식이 한 개의 return 문장만을 가지고 있을 경우. 컴파일러는 해당 return 표현식 으로 부터 리턴 타입을 추론할 수가 있습니다. | | | 01: #include <iostream>
02: #include <algorithm>
03: #include <vector>
04: using namespace std;
05:
06: int main()
07: {
08: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
09: vector<double> v1(10);
10: vector<double> v2(10);
11:
12: // return 문장이 한 개 입니다. "?>double" 는 생략 가능 합니다.
13: transform( x, x+10, v1.begin(), [](int n) { return n / 2.0;});
14:
15: // return 문이 없습 니다. “?>void” 는 생략 가능 합니다.
16: for_each( v1.begin(), v1.end(), [](double d) { cout << d << " ";});
17: cout << endl;
18:
19: // 2개 이상의 return 문이 있습니다. 반드시 "?>double" 을 표시 해야 합니다.
20: transform( x, x+10, v1.begin(), [](int n)->double {
21: if ( n % 2 == 0 ) return n;
22: return n / 2.0;
23: });
24:
25: for_each( v1.begin(), v1.end(), [](double d) { cout << d << " ";});
26: cout << endl;
27:
28: // 역시 1개의 return 문 입니다.
29: transform( x, x+10, v1.begin(), v2.begin(), [](int a, double d){ return a + d;});
30: for_each( v2.begin(), v2.end(), [](double d) { cout << d << " ";});
31: cout << endl;
32: } |
| | | 4. Capturing Local Variable | | | C++에서 함수 객체를 사용하는 이유 중의 하나는 상태를 가질 수 있다는 점입니다. 지금까지의 람다 표현식은 상태를 가지지 않았습니다. 하지만 지역변수를 캡쳐(capture) 하므로서 람다 표현식도 상태를 가질수가 있습니다. 아래 예제를 보겠습니다. | | 01: int main()
02: {
03: int x[10] = { 1,3,5,7,9,11,13,15,17,19};
04:
05: int low = 12;
06: int high = 14;
07:
08: int* p = find_if( x, x+10, [low, high](int n) { return low < n && n < high;});
09:
10: cout << *p << endl;
11: } |
| | | Lambda introducer 인 “[]” 안에는 지역변수를 넣으 므로서 해당 지역변수를 람다 표현식안에서 사용할수있습니다. 이러한 것을 “capture list” 라고 합니다. “[low, high]”라고 표시하므로서 low, high 지역변수를 람다표현식 안에서 사용할 수 있습니다. “capture list”를 사용하지 않을 경우 람다 표현식 안에서 지역변수에 접근할 수는 없습니다. | | 결국 위 표현식은 아래코드와 같은 의미를 가지게 됩니다. | | 01: class closure_object
02: {
03: private:
04: int min;
05: int max;
06: public:
07: closure_object( int l, int h ) : min(l), max(h) {}
08: bool operator()(int n) const { return min < n && n < max; }
09: };
10:
11: int main()
12: {
13: int x[10] = { 1,3,5,7,9,11,13,15,17,19};
14: int low = 12;
15: int high = 14;
16:
17: int* p = find_if( x, x+10, closure_object(low, high) );
18: cout << *p << endl;
19: } |
| | 디폴트 캡쳐인 “[=]”를 사용하면 모든 지역 변수를 람다 표현식에서 사용할 수 있습니다. | | 1: int main()
2: {
3: int x[10] = { 1,3,5,7,9,11,13,15,17,19};
4: int low = 12;
5: int high = 14;
6:
7: int* p = find_if( x, x+10, [=](int n) { return low < n && n < high;});
8: cout << *p << endl;
9: } |
| | “[low, high”] 또는 “[=]” 는 모두 지역변수를 값으로(by value)로 캡쳐 합니다. 아래 와 같이 사용 하므로서 지역변수를 참조(by reference)로 캡쳐 할 수 있습니다. 즉, 람다 표현식 안에서 지역변수의 값을 변경 할 수 있습니다. | | 1: int main()
2: {
3: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
4: int sum = 0;
5:
6: // x~x+10 에서짝수의합을구하는람다표현식입니다.
7: for_each( x, x+10, [&sum] (int n) { if ( n % 2 == 0 ) sum += n;});
8:
9: cout << sum << endl;
10: } |
| | “[&sum]” 은 지역변수 sum 을 람다 표현식에서 참조로 사용하겠다는 의미 입니다. 즉, 아래의 코드와 동일한 의미를 가지게 됩니다. | | | 01: class closure_object
02: {
03: private:
04: int& ref;
05: public:
06: closure_object ( int& r ) : ref(r) {}
07: void operator()(int n) const { if ( n % 2 == 0 ) ref += n; }
08: };
09:
10: int main()
11: {
12: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
13: int sum = 0;
14:
15: for_each( x, x+10, closure_object( sum ) );
16: cout << sum << endl;
17: } |
| | | “[&]”를 사용 하므로서 모든 지역변수를 참조를 캡쳐 할 수도 있습니다. | 또한 모든 지역변수를 값으로([=]) 디폴트 캡쳐를 사용하면서도 특정 변수만을 참조로 캡쳐 할 수도 있습니다. | | 01: int main()
02: {
03: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
04: int low = 3;
05: int high = 8;
06: int sum = 0;
07:
08: // low ~ high 사이의 합을 구하는 람다 표현식 입니다.
09: for_each( x, x+10, [=, &sum](int n) { if ( low < n && n <high) sum += n; });
10: cout << sum << endl;
11: } |
| | 이번에는 멤버 함수 안에서 람다 표현식을 사용하는 경우를 생각해 봅시다.. | | 01: class Test
02: {
03: public:
04: void foo() const
05: {
06: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
07:
08: // 멤버 data인 base를 캡쳐 하려고 한다. 하지만 error.
09: for_each( x, x+10, [base](int n) { cout << base + n << " ";});
10: cout << endl;
11: }
12:
13: Test() : base(100) {}
14: private:
15: int base;
16: };
17:
18: int main()
19: {
20: Test t;
21: t.foo();
22: } |
| | 이 경우에는 아래처럼 [this], 또는 [=] 를 사용하면 멤버 data 를 캡쳐할 수 있습니다. | | 01: class Test
02: {
03: public:
04: void foo() const
05: {
06: int x[10] = { 1,2,3,4,5,6,7,8,9,10};
07:
08: for_each( x, x+10, [this](int n) { cout << base + n << " ";}); // 또는 [=]
09: cout << endl;
10: }
11: Test() : base(100) {}
12: private:
13: int base;
14: };
|
| | | | 5. Nullary Lambda | | | 파라미터를 갖지 않는 람다 표현식이 만들수 도 있습니다. 이 경우 파라미터 리스트의 ()를 생략할 수도 있습니다. 다음 코드를 살펴 봅시다. | | | 01: int main()
02: {
03: int x[10] = {0};
04: int y[10] = {0};
05: int i = 0;
06: int j = 0;
07:
08: generate( x, x+10,[&i]() { return ++i;});
09: generate( y, y+10,[&j] { return ++j;}); // () 를생략해도된다.
10:
11: for_each( x, x+10, [](int n) { cout << n << " ";});
12: cout << endl;
13:
14: for_each( y, y+10, [](int n) { cout << n << " ";});
15: cout << endl;
16: } |
| | | | 6. Mutable Lambda | | 클로져 객체의 함수 호출연산자(operator())는 상수 함수로 만들어 집니다. 따라서, 지역 변수를 캡쳐해 사용하는 람다 표현식은 자신의 상태를 변경 할 수 없습니다. 이경우 mutable 키워드를 사용 하면 값으로 캡쳐한 지역변수의 값을 변경할 수 있습니다. | | 01: int main()
02: {
03: int x[10] = { 1,3,5,7,9,2,4,6,8,10};
04: int i = 0;
05:
06: // error. 값으로캡쳐한지역변수의값을변경할수없다.
07: for_each( x, x+10, [=](int n) { cout << ++i << " : " << n << endl;});
08:
09: // ok.
10: for_each( x, x+10, [=](int n) mutable { cout << ++i << " : " << n << endl;});
11: } |
| | boost에서 지원하던 람다개념을 C++0x에서는 언어 차원에서 지원하므로서 STL을 훨씬 편리 하게 사용할 수가 있게 되었습니다. | | | | |
|
|