c++ Lambda

2014. 6. 12. 11:08C++

http://psychoria.blog.me/40171894460


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 람다의 문법에 대해서 확인해 보았는데요. 

 

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

 

람다의 구문을 자세히 보도록 하겠습니다. 

 

람다의 구조를 설명한 이미지를 다시 한 번 보고 설명하겠습니다. 

 

 

 

1. 캡처(Capture) 

 

캡처는 해당 람다 구문을 포함하고 있는 Scope에 선언된 변수를 사용할 수 있게 해줍니다. 

 

캡처는 이미지에서 [=]로 표시된 부분입니다. 

 

기본적으로 []를 사용하면 Scope의 변수에 접근이 불가능하게 됩니다. 

 

 

 

코드에서 return 0; 위의 } 뒤에 ();가 붙는 것은 람다를 정의하고 바로 호출하기 위해서 입니다. 

 

[]로 했을 때 주석 부분은 에러로 처리됩니다. 

 

바깥의 변수를 캡처하지 않았기 때문입니다. 

 

Scope내의 모든 변수를 사용하기 위해서는 [=] 혹은 [&]를 사용하면 됩니다. 

 

[=]는 a, b 모두 call by value 형태로 캡처하고 

 

[&]는 a, b 모두 call by reference 형태로 캡처합니다. 

 

mutable 키워드에서 다시 설명하겠지만 

 

[=]로 캡처했을 때 주의 하실 점은 mutable 키워드를 사용하지 않으면 값의 변경이 되지 않습니다. 

 

mutable 키워드를 사용해서 값을 변경해도 

 

call by value 형태기 때문에 원래 a는 값이 변하지 않습니다. 

 

또 각 변수에 각각 옵션을 주는 것도 가능합니다. 

 

 

 

a는 call by reference로, b는 call by value로 캡처했고 

 

c는 캡처하지 않았기 때문에 주석을 풀면 에러가 납니다. 

 

주의 하실 점은 call by value로 변수 하나를 캡처할 때는 =을 붙이지 않습니다. 

 

즉, =b가 아니라 그냥 b입니다. 

 

[&, a]는 a만 call by value로 나머지 변수(b, c)를 call by reference로 캡처합니다. 

 

[=, &a]는 a만 call by reference로 나머지 변수(b, c)를 call by value로 캡처합니다. 

 

정리하면 캡처는 Scope 내의 변수를 람다 내부에서 사용할 수 있게 해줍니다. 

 

경우에 따라서는 외부 변수를 사용하지 않게 할 수도 있고 

 

일부만 call by value, call by reference로 캡처할 수도 있고 

 

전부 캡처할 수도 있습니다. 

 

2. 파라미터 리스트(Parameter List) 

 

파라미터 리스트는 일반적인 함수의 파라미터 리스트와 거의 유사합니다. 

 

거의 유사하다는 것은 다른 점이 존재한다는 것인데 차이점은 다음과 같습니다. 

 

1) 디폴트 인자(Default Argument)를 사용할 수 없다. 

2) 가변 인자(Variable-length Argument list)를 사용할 수 없다. 

3) 이름 없는 인자(Unnamed Parameters)를 사용할 수 없다. 

 

하나만 남기고 선을 그은 이유는 저 세 가지 이유가 msdn에 적혀 있으나 

 

실제 코드로 테스트 해 본 결과 가변 인자나 이름 없는 인자는 사용이 가능했습니다. 

 

다만 int n = 0 같은 디폴트 인자는 사용할 수 없습니다. 

 

3. mutable 

 

mutable 키워드는 원래 C++에서 const 멤버 함수에서는 멤버 변수 변경이 불가능하나 

 

예외적으로 const 멤버 함수에서도 수정이 가능하도록 멤버 변수를 선언할 때 사용합니다. 

 

즉, mutable로 선언된 멤버 변수는 const 멤버 함수에서도 수정이 가능합니다. 

 

람다에서의 mutable은 call by value 형식으로 캡처한 변수를 수정할 수 있게 해줍니다. 

 

[=]이나 [a]로 캡처해서 사용할 수 있게 된 변수는 기본적으로 값의 수정이 불가능합니다. 

 

다음과 같은 코드에서 주석을 해제하면 에러가 발생합니다. 

 

 

 

Visual Studio 2012에서는 다음과 같은 에러를 출력합니다. 

 

error C3491: 'a': a by-value capture cannot be modified in a non-mutable lambda 

 

다음과 같이 수정하면 정상적으로 실행이 됩니다. 

 

 

 

[&]나 [&a]와 같이 call by reference로 캡처하면 mutable을 쓰지 않아도 값 변경이 가능합니다. 

 

4. throw 

 

throw()는 일반적인 함수에서 사용하는 구문과 동일합니다. 

 

함수의 뒤에 throw(int)와 같은 형태로 안에 타입을 지정해주면 

 

함수가 int 타입으로 예외를 전달할 것이다라고 알려주는 일종의 주석의 역할을 합니다. 

 

throw(int)로 해놔도 int가 아닌 타입으로도 예외 전달이 가능합니다. 

 

throw()의 경우는 의미가 달라지는데 함수가 예외를 던지지 않겠다는 의미가 됩니다. 

 

 

 

throw()를 사용했을 때 예외를 던진다고 해서 에러가 발생하진 않고 warning C4297이 발생됩니다. 

 

경고는 발생되지만 예외 처리는 정상적으로 가능합니다. 

 

5. Return Type 

 

람다역시 다른 함수와 마찬가지로 리턴 타입을 지정할 수 있습니다. 

 

리턴 타입은 -> 타입의 형태로 지정합니다. 

 

리턴 타입은 생략이 가능한데, return 문을 보고 타입을 판단합니다. 

 

주의 하실 점은 return이 여러 군데 있을 때는 타입을 동일하게 해야 됩니다. 

 

람다는 람다를 리턴하거나 람다를 람다의 인자로 넣을 수 있는데 

 

이러한 속성을 Higher-order Function이라고 합니다. 

 

 

 

명시적으로 int 타입을 리턴하게 해주고 int 값 0을 리턴했습니다. 

 

6. Body 

 

함수의 몸체 부분은 기존의 함수 구현과 동일하게 사용이 가능합니다. 

 

다만 캡처한 변수들을 사용할 수 있다는 점이 다릅니다. 

 

람다는 함수 포인터에 비해 더 많은 기능을 제공할 수 있고, 

 

코드 상에서도 함께 해야할 코드를 묶어주는 것이 가능합니다. 

 

기존의 Function Object를 사용하면 알고리즘과 알고리즘을 사용하는 부분이 분리되지만 

 

람다는 한 곳에 둘 수 있습니다. 

 

람다는 C++에서 유용하게 쓰일 것으로 생각됩니다. 

 

처음 보면 생소한 문법이지만 문법을 작은 단위로 쪼개서 확인하면 

 

일반 함수와 크게 다르지 않아서 문법을 이해하는 것은 어렵지 않습니다.