본문 바로가기

3.구현/C or C++

[C++0x] 람다식

들어가기

최근 C++의 표준인 C++0x 람다 함수가 추가되었다. 람다식 또는 람다함수는 기존 함수와 동일하다. (이후 람다함수) 단지 다른 부분은 익명(anonymous) 함수일 뿐이다. 익명이라는 것은 함수 명이 없다는 것이기에 기존 함수 선언과 같이 사용할 수 없을 뿐이다.
이 익명함수은 다른 언어에서도 많이 사용하고 있고 아주 유용하게 사용되고 있다.

작성: http://ospace.tistory.com/,2012.04.27 (ospace114@empal.com)

기본

기본 형은 다음과 같다.

[] {};

[]가 람다 삽입자(Lamda-introducer)이며, 뒤에 {}은 함수 블록과 동일하다. 삽입자라고 하는 이유는 선언에 의해서 다른 곳에서 재사용하기 위한 것이 아니라 그곳에만 삽입되어 사용되기 때문이다.
이는 아주 기본형이기 때문이 넘겨 받는 인자나 리턴 값에 대한 부분은 없다. 단순히 리턴하는 경우는

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

뒤쪽 괄호는 람다 함수를 호출한다. 호출해서 반환값이 없었다. 그럼 인자는 어떻게 받을까?

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

LambdaExpression.JPG

출처: http://www.codeproject.com/Articles/71540/Explicating-the-new-C-standard-C-0x-and-its-implem

지금은 반환형이 지정되어 있지 않다. bool형이 되어 있지만, 리턴형이 지정되어 있지 않다. 이 것 역시 지정할 수 있다.

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

Capture Specification.

람다는 Stateful이나 Stateless 중에 하나이다. 이런 상태는 상위 범위에있는 변수를 어떻게 접근할 것이지를 정의한다. 보통 다음과 같은 종류 중에 하나일 뿐이다.

  • 상위 범위로 접근할 수 있는 변수가 없음.
  • 상위 범위의 변수로 읽기만 가능.
  • 변수가 람다에 복사가되고, 복사본을 수정 가능하다.(call-by-value와 비슷)
  • 상위 범위 변수를 모두 엑세스 가능하다. 심지어 수정까지 가능하다.

이를 다음과 같이 정리할 수 있다.

  • private 변수
  • const 변수
  • passed-by-value
  • passed-by-reference

이를 람다 함수에서는 어떻게 표현할까

  • [] - 아무것도 접근 불가
  • [=] - 모든 변수에 값 의해 접근
  • [&] - 모든 변수에 참조 의한 접근
  • [var] - var 변수를 값 의한 접근. 나머지는 불가
  • [&var] - var 변수를 참조 의한 접근. 나머지는 불가

여러 개를 사용하면 콤마로 구분하여 사용한다.

int a,b;
//...
int sum = [a,b] { return a+b; } ();

일부 변수에 대해 별도로 적용함. 모든 변수는 값에 의해 접근할 수 있고, sum이란 변수는 참조에 의한 접근이 가능하다.

[=, &sum] {
sum = a+b+c;
} ();

mutual 키워드

괄 호 뒤에 mutual 제한자를 사용할 수 있다. 이를 같이 사용하면 모든 by-value 변수가 call-by-value 모드로 넣는다. Mutual을 사용하지 않는다면 모든 -by-value 변수는 상수이다. 즉 람다에서 이를 수정할 수 없다. Mutual은 컴파일러에게 모든 변수의 복제본을 만들라고 강제를 하는 것이고 이들은 by-value에 의한 변수들이다. 모든 by-value에 의한 변수는 수정할 수 있다. const와 non-const를 선별적으로 사용할 수 없다. [2]

int x=0, y=0, z=0;
[=]()mutable->void //mutable을 사용할 때 ()은 필요.
{
   x++; // 모든 변수는 call-by-value 형태로 사용할 수 있다. 컴파일러가 y,z가 사용하지 않는다고 경고를 뛰운다.
} (); // 여전히 x는 0으로 남아 있다.

x는 복제본이고, 복제된 값을 변경했기에 원본은 변경되지 않는다. 흥미로운 부분은 y, z에 대한 not used라는 경고문이다.

function 클래스 객체 이용

람다는 실제로는 클래스이다. 이 들을 function 클래스에 저장할 수 있다. 이 클래스는 std::tr1에 속해 있다. 간단한 예를 보자.

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

간단한 예

vector<int> v = {50, -10, 20, -30};
std::sort(v.begin(), v.end());    // 기본 정렬
// 이제 v는 { -30, -10, 20, 50 }이다
// 절대값으로 정렬:
std::sort(v.begin(), v.end(), [](int a, int b) { return abs(a)<abs(b); });
// 이제 v는 { -10, 20, -30, 50 }이다

람다식 표현: [](int a, int b) { return abs(a)<abs(b); }
대괄호("[]")은 로컬 네임이 람다 식에 전달하는 방법을 명시하는 "캡처 목록".
[]은 아무 값도 캡처하지 않음
[&]으로 쓰면 모든 값을 참조에 의한 전달
[&v]로 쓰면 v만 캡처
[=v]로 하면 값에 의한 전달

void f(vector<record>& v)
{
    vector<int> indices(v.size());
    int count = 0;

    //[&]에 의해 모든 로컬 변수(count포함)를 참조
    fill(indices.begin(),indices.end(),[&](){ return ++count; });

    struct Cmp_names {
        const vector<Record>& vr;
        Cmp_names(const vector<Record>& r) :vr(r) { }
        bool operator()(Record& a, Record& b) const { return vr[a]<vr[b]; }
    };

    // record의 이름 필드에 의해 순서를 정해서 인덱스를 정렬:
    std::sort(indices.begin(), indices.end(), Cmp_names(v));
    // ...
}

결론

람다식으로 인해 코드가 더 간결하게 처리할 수 있다. 어떻게 보면 코드가 더 복잡해보일 수가 있다. 사용하는 사람에 따라서 달라진다는 말이다. 이와 사용할거 제대로 알고 사용하는게 좋지않을까? 모두 즐프하세요. ospace.

참조

[1] C++0x FAQ

[2] http://www.codeproject.com/Articles/71540/Explicating-the-new-C-standard-C-0x-and-its-implem

[3] http://frompt.egloos.com/2770424

반응형

'3.구현 > C or C++' 카테고리의 다른 글

데이터 값을 비트 문자열로 변환  (0) 2012.08.14
고급 매크로 기법 Variadic macro  (0) 2012.08.14
[C++] 콘솔입력(pipe) 성능 비교  (0) 2011.12.29
[C++] 파일 읽기 성능 비교  (0) 2011.12.28
C++ Delegate 구현원리  (2) 2011.01.17