C++11, Lambda Expressions #1

Short Articles 2012. 1. 30. 18:41
이미 C++11 의 람다 표현식에 대한 내용이 여러 블로그들에 많이 올라와 있지만
다시 정리해본다는 생각으로 C++11 표준 문서와 MSDN 에 올라온 내용을 바탕으로 정리해보겠습니다.
이 코드는 C++11 표준 문서 5.1.2 처음에 나오는 샘플 코드를 약간 수정한 것입니다.
위 코드에서는 배열 정렬 확인을 위한 출력 코드 삽입하면서 람다 표현식이 한번 더 사용되었습니다.
람다 표현식을 쓰지 않고 기존 방식으로 Functor 를 사용한다면
아마도 다음과 같은 식의 코드가 작성 될 것입니다.

이 두 코드를 일단 코드의 재활용성 측면에서 보자면..
절대값 비교를 위한 Functor 가 여러 곳에서 자주 사용되는 경우에는
매번 람다 표현식을 통해 평가식을 작성하는 것 보다는 Functor 를 한번 작성해 두고
이 것을 계속 재활용 하는 편이 훨씬 이득이 클 것입니다.
하지만, 단 한 번 사용될 평가식을 위해서 Functor 를 정의 하는 것은 비효율적 이겠지요.
더불어 Functor 의 이름 짓기도 다소 신경이 쓰이는 부분입니다.

언어학적으로도 그렇지만 프로그래밍에서도 적절한 메타포 의 작성은 상당히 중요합니다.
만약 다음과 같이 구조체의 이름을 정의한다면
이것을 보게 될 누군가는 작성자를 정말 썰어버리고 싶어질지도 모릅니다.


이런 어의없는 경우가 아니더라도
본인은 정말 기가막히게 메타포를 연결했다고 생각하는 것이
그것을 보는 상대방은 전혀 납득이 가지 않을 수도 있습니다.

지속적으로 재활용할 코드(평가식)도 아니고
어떤 이름을 붙여야할지 애매한 Functor 를 작성해야 할 때,
이럴 땐 오히려 그냥 코드의 내용을 풀어 보여주는 편이 나을 수 있습니다.
이 때 유용하게 쓸 수 있는 것이 람다 표현식이고
람다를 또 다른 말로는 '이름 없는 함수', '익명 함수' 라고 합니다.

C++11 표준 문서 5.1.2 Lambda Expressions 첫 부분을 보면 바로 다음과 같이 설명하고 있습니다.
Lambda Expressions provide a concise way to create simple function objects.
이 문장과 함께 예로 나온 코드가 바로 앞서 본 절대값 정렬을 하는 람다 표현식입니다.

이 외에도 람다 표현식의 주요 용법 중 하나로 Lazy Evolution (또는 Late Evolution)에 대한 부분도 나오는데
이건 나중에 살펴보기로 하고.. 일단 람다 표현식의 문법적인 부분을 살펴 보겠습니다.

람다 표현식은 가장 단순하게 적으면 다음과 같이 적을 수 있습니다.

[ ] ( ) { }




여기서 [] 부분을 lambda-introducer, () 부분을 lambda-declarator, {} 부분을 compound-statement 라고 합니다.
(lambda-declarator 는 생략 가능합니다.)

+ lambda-introducer
lambda-introducer 는 lambda-capture 를 포함 할 수 있습니다.
[=, a, &b, c] 이런식으로요.
lambda-capture 는 capture-default 라는 놈과 capture-list 라는 놈이 있는데
하나만 사용할 수도 있고, 둘 다 사용할 수도 있습니다.
단, 둘 다 사용할 경우에는 capture-default 가 먼저와야 합니다.

그럼 lambda-capture 부터 살펴 보겠습니다.
lambda-default 는 =, & 이렇게 딱 두개가 존재하는데
capture 에 대한 기본 속성을 지정해 주는 역할을 합니다.
'&' 는 by-reference capture 라고 하고 
뒤이어 오는 capture-list 는 &로 시작하는 identifier 는 올 수 없습니다.
'=' 는 by-value capture 라고 하고
뒤이어 오는 capture-list 는 this 와 일반적인 identifier 는 올 수 없고, 
&로 시작하는 identifier 만 올 수 있습니다.


몇 가지 예를 들어 보겠습니다.

(1) by-value capture
이 람다식은 lambda-declarator 는 생략 되었고, 뒷 부분에 오는 () 는 function call operator 입니다.
아무튼 이 코드는 잘 동작하고 sum 의 값은 2로 계산되어 출력될 것입니다.

하지만 람다식 내에서 a,b,c 에 대해서 전위 연산을 하려 할 경우
다음과 같은 에러 메시지를 보게 되는데..

error C3491: 'a': 변경 불가능한 람다에서 값 방식 캡처를 수정할 수 없습니다.

error C3491: 'b': 변경 불가능한 람다에서 값 방식 캡처를 수정할 수 없습니다.

error C3491: 'c': 변경 불가능한 람다에서 값 방식 캡처를 수정할 수 없습니다.


이것은 위의 람다식에서 closure 의 inline function call operator 가 const 로 지정되기 때문입니다.
필요한 경우 mutable 키워드를 붙여주면 람다식 내에서 a,b,c 에 대한 값 변경 가능해집니다.


아무튼 여기서 중요한 것은 a,b,c 는 value-capture 하였기 때문에
람다식 내에서 a, b, c 값을 수정 하더라도 a, b, c 의 값이 변경 되지 않는다는 것입니다.
수정값은 람다식 내에서만 유효합니다.

여기서 잠깐..
클로저(closure)의 개념에 대해 생소하시다면 잠시 클릭 -> 클로저 

람다 표현식이 closure 를 포함 하기 때문에 capture 시점의 상태를 저장할 수 있고
이를 통해서 앞서 잠시 언급했던 lazy evolution 이 가능해 집니다.
일단 상태만 저장하고 있다가 프로그램이 한가해지는 (적절한) 시점에서 값을 평가(계산)해서
프로그램의 부하가 분산되도록 유도하는 것이죠.

예를 들면 다음과 같이 배열의 초기값을 기억하고 있다가 나중에 계산이 필요한 시점에서
사용하는 식으로요.

12번 줄에서 float 배열 fa 의 값들은 value-capture 되어
std::function 을 통해서함수 바인딩되어 lst 에 저장됩니다.
그리고 중간에 fa 의 값이 마구 변경이 되더라도나중에 lst 에 저장 되었던
람다 표현식의 closure 의 function call operator 를 호출하게 되면
capture 당시 저장했던 값 그대로 이용하여 연산식을 수행하고 결과값을 출력해 줍니다. 

(2) by-reference capture
reference capture 를 하게 되면 value capture 와는 다르게 람다 표현식 내에서의 수정이
외부에도 동일하게 적용 됩니다. 일반적인 C++ 함수에서 파라미터를 reference 로 받는 것 같이요.

위에서 예를 들었던 코드를 by-reference capture 로 바꿔서
다음과 같이 사용하면..
이 람다 표현식에서는 fa 의 요소들을 reference 로 capture 하였기 때문에
출력값을 확인하면 모두 0.912945 가 나옵니다. 

만약 sin(f*2.f + 20.f) 에서 고정값인 20.f 대신에 외부에서 계속 변경되는 변수의 값을 적용되도록 하되
f 는 앞서 value-capture 에서 본 것처럼 초기값을 그대로 사용하고 싶다면 어떻게 하면 될까요?
default capture 로 by-reference capture 를 지정하고 capture-list 에 identifier f 를 적어주면 되겠죠.
계산식은 값 확인이 편하도록 단순하게 고쳤습니다.

결과값은 다음과 같이 나올 것입니다.

0.1
2.1
0.8
10
0.02

20.1
22.1
20.8
30
20.02 


배열 fa 의 값은 capture 당시값 그대로를 유지하지만 p 의 값은 변경한대로 전달이 되었습니다.

짧게 쓰려고 했는데 글이 생각보다 길어지네요.
다음 글에서 이어서 적겠습니다.

 

:

제4판 Windows 시스템프로그래밍

Book Review 2012. 1. 30. 14:09


이책을 구입하였습니다 

윈도우즈 API 핵심 바이블이라는 말답게
핵심적인 이야기가 담겨져 있습니다
각 쳅터가 간결하고 분량도 670페이지 정도
짧고 목차도 찾기 쉽게 되어 있어
레퍼런스로 활용 가능할 듯 합니다

유닉스와 비교하게 되어 있어
유닉스 시스템 프로그래머도 쉽게 접근할 수 있습니다

Windows via C/C++와 비교한다면
분량은 970페이지 정도
일부 API의 경우 문서화 되지 않았지만 대충 내부단까지 설명해준 경우도 있고
파일이라는 추상적인 범주안에 들어가는 것들을 상세히 열거합니다.( 그래서 책 보다가 머리가 좀 아프다 )
세세하게 설명해주는 책

그에 비해 windows 시스템 프로그래밍은
핵심만 설명한책 입니다 

'Book Review' 카테고리의 다른 글

[C++11 Overview] enum  (0) 2013.02.18
[C++11 Overview] nullptr  (0) 2013.02.18
[C++11 Overview] Range-Based for Loops  (0) 2013.02.18
[C++11 Overview] Counting Words Across Files  (0) 2013.02.18
이야기로 아주 쉽게 배우는 대수학  (0) 2012.02.22
: