람다는 C++11에 새로 등장한 문법이다.
기존에 클래스를 이용해서 만들어 쓰던 함수 객체(Functor)를 문법에 추가한 것이다.
int a = 3; int b = 4; auto test = [&a, b] (int c) -> int { a = a + b + c; return c; } std::cout<<test(3)<<std::endl; std::cout<<a<<std::endl;
출력
3 10
문법
[] () {}
세 쌍의 괄호들로 이루어진다.
1. []
변수를 캡쳐하는 부분이다.
람다가 정의되는 부분에서 접근 가능한 변수들을 저장해놓는 역할을 한다.
람다는 std::function이나 템플릿 유추 등을 통해 함수 포인터로써 기능하는 경우가 많다.
[]을 통해서 변수를 캡쳐하게 되면, 캡쳐하는 순간의 그 변수의 정보를 람다 함수가 들고 있게 된다.
캡쳐를 한다는 것은, 람다 객체의 멤버 변수로 넣겠다는 말과 같다.
위에서 적었다시피, 람다는 Functor를 문법으로 만든 것이고,
원래 Functor는 클래스로 만들었다.
Functor를 쓸 때 외부 변수가 필요할 경우, 멤버 변수에 값을 넣어둔 다음에
그 멤버 변수의 값을 사용했다.
그 부분이 람다에서는 캡쳐이다.
외부의 변수를 람다 내부에서 사용할 수 있도록 하는 것이다.
캡쳐에는 2종류가 있다.
값을 캡쳐하는 것과 주소를 캡쳐하는 것.
int a = 3;
이런 변수가 있을 때,
[a] - a의 값을 캡쳐한다.
[&a] a의 주소를 캡쳐한다.
a의 값을 캡쳐하는 경우,
a는 const 속성이 붙게 된다.
값을 변경할 수 없게 되는 것이다.
뒤에 적을 mutable 키워드를 붙이면 값의 변경은 가능하지만,
람다 안에서 a의 값을 바꾸더라도
람다 밖의 원본 a의 값은 전혀 변하지 않는다.
하지만 람다 안에서의 a는 바뀐다.
위에서 말했다시피, 람다에서 캡쳐는 멤버 변수의 역할을 한다.
외부 변수의 값을 가져온 다음 아무리 바꿔봤자 외부 변수는 바뀌지 않는 것과 같다.
int a = 3; auto lambda = [a]() mutable { ++a; std::cout<<a<<std::endl; } lambda(); lambda();
이 코드를 실행하면,
4 5
이렇게 출력이 된다.
a의 주소를 캡쳐하는 경우,
람다 안에서 a를 사용하는 것은 캡쳐된 변수 a의 레퍼런스를 사용하는 것이 된다.
이 때 주의해야 할 것은,
레퍼런스로 캡쳐할 경우, 람다가 실제로 수행될 때 그 원본 변수가 제대로 살아있는지 여부를 확인해야 한다.
Dangling pointer를 주의해야 하는 것이다.
예를 들어, 어떤 함수 안에서 선언된 변수를 레퍼런스로 캡쳐한 후,
그 함수를 빠져나간 다음에 람다가 실행되게 되면
람다는 잘못된 메모리에 접근하는 것이 된다.
캡쳐할 때, 기본 캡쳐 방식을 지정할 수 있다.
[=] 이렇게 쓰게 되면, 모든 외부 변수를 값으로 캡쳐하고,
[&] 이렇게 쓰게 되면, 모든 외부 변수를 주소로 캡쳐하게 된다.
기본 캡쳐 방식을 지정한 후에, 기본 캡쳐 방식과 다른 방식으로 캡쳐하고 싶은 변수들을 따로 캡쳐할 수 있다.
[=, &a, &b, &c] 이런 식이나,
[&, a, b, c] 이런 식으로.
기본 캡쳐 방식과 같은 방식의 추가 캡쳐는 못 한다.( [=, a] , [&, &b])
특수한 상황이 한 가지 있다.
클래스 안에서 선언된 람다의 경우,
멤버 변수는 캡쳐할 수 없다.
대신 this 포인터를 값으로 캡쳐해서 사용할 수 있다.
this는 주소로 캡쳐할 수 없다.
당연하지만, 아무것도 캡쳐하고 싶지 않을 경우
그냥 []를 쓰면 된다.
2. ()
일반적인 함수의 인자를 받는 것과 똑같다.
std::function<void(int)> print = [](int a) { std::cout<<a; }); print(4);
이러면 4를 출력한다.
<algorithm> 헤더에 있는 함수들 중
인자로 함수 포인터 또는 Functor를 받는 함수가 있다.
std::for_each 가 대표적인데,
첫 번째 인자와 두 번째 인자로 자료구조의 시작과 끝을 받고,
세 번째 인자로 함수 포인터나 Functor를 받는데,
그 함수의 인자는 첫 번째와 두 번째로 받은 자료구조의 자료형이어야 한다.
std::vector<int> container; std::for_each(container.begin(), container.end(), [](int value) { std::cout<<value; }
3. {}
일반적인 함수에서의 역할과 같다.
람다 함수의 본문을 넣는 곳이다.
4. 리턴 타입
람다도 함수이니만큼, 리턴을 할 수 있다.
리턴은 두 가지 방식으로 한다.
1) 암시적 리턴
람다 본문 내부에서 return 키워드가 한 개만 쓰일 경우,
람다의 리턴 타입은 리턴하는 값/변수의 타입이 된다.
int 변수를 리턴할 경우 리턴 타입은 int이고,
Widget* 변수를 리턴할 경우 Widget*,
true를 리턴할 경우 bool이 되는 식이다.
2) 명시적 리턴
[] () -> 리턴 타입 { 함수본문 }
이런 식으로 리턴 타입을 명시적으로 나타낼 수 있다.
[] () -> int { return 3.0f; }이렇게 할 경우, 일반적인 함수에서와 같이
3.0f가 int로 형변환 되서 리턴이 된다.
리턴 문이 2개 이상일 경우,
그 어떤 경우에도 암시적 리턴을 쓸 수 없다.
반드시 명시적 리턴을 써야 한다.
if(조건) return false; else return true;
이런 식으로 모든 리턴문의 리턴 타입을 같게 해주었더라도,
명시적 리턴을 사용해야 한다.
5. 키워드
함수에서 () 다음에 const, override 같은 키워드를 붙이듯이,
람다에서도 () 다음에 키워드를 붙일 수 있다.
mutable, exception, attribute 이렇게 세 개의 키워드를 쓸 수 있다.
mutable
값으로 캡쳐한 변수의 경우, 기본적으로 const로 취급이 된다.
mutable을 붙일 경우, const로 취급하지 않는다.
exception과 attribute는 각각 C++의 예외 처리 문법, 그리고 attribute 키워드에 대응한다.
이 쪽은 잘 모르는 데다가, C++ 기본 문법과 똑같은 역할을 한다고 하니 패스...
'C++' 카테고리의 다른 글
NodeJS C++ addon 바이너리 데이터 넘기기 삽질기 (0) | 2014.07.08 |
---|