'Short Articles'에 해당되는 글 16건

  1. 2012.02.17 C++ Template, Expression Templates #2
  2. 2012.02.10 C++ Basic Concept, Storage Duration
  3. 2012.02.08 C++ Template, Expression Templates #1 3
  4. 2012.02.07 C++ Basic Concept, One definition rule (ODR) 2
  5. 2012.02.01 C++11, Lambda Expressions #2
  6. 2012.01.30 C++11, Lambda Expressions #1 2

C++ Template, Expression Templates #2

Short Articles 2012. 2. 17. 20:14
표현식 템플릿은 숫자 배열 클래스를 지원하기 위해 고안된 프로그램이 기술입니다
이번 아티클은 다음과 같이 진행합니다
1. 단순한 배열 클래스 구현하여 배열 연산에 대한 문제제기
2. 표현식 템플릿 구현
3. 표현식 템플릿을 아는 랩퍼클래스 구현
4. 실재 표현식 템플릿 사용해서 어떻게 동작하는 방법 추적

우리가 실질적으로 해야 하는 배열 연산입니다
배열을 어떻게 효율적으로 연산을 할까요?

이번에 사용되는 표현식을 살펴봅시다  1.2*x + x*y 
연산트리
연산트리로 표현하였습니다
여기에서 X,Y가 배열 클래스 입니다
1.2는 스칼라를 나타내는 클래스로 변경되고요
+,* 는 바로 표현식 템플릿이 되는 겁니다

전체 표현식을 다 읽기 전에는 표현식의 일부만
계산하지 않고 전체 계산하기전에 어떤 객체에
무슨 연산이 적용되었는지 기록하는 것

표현식 템플릿의 핵심입니다

 이 트리를 전위순회로 변경하면 다음과 같습니다




   이 트리를 전위순회로 변경하면 다음과 같습니다  


+, *, 1.2 가 클래스 템플릿으로 변경하고 주어진 표현식을 다음과 같은 데이터형을 가지는 객체로 바꿔봅시다

A_Add<  A_Mult<A_Scalar<double>, Array<double> >,
    A_Mult<Array<double>, Array<double> > >
새로운 배열 Array 클래스 템플릿과 A_Scalar, A_Add, A_Mult 와 함께 사용되었습니다

이제 본격적으로 알아봅시다
먼저 덧셈을 나타내는 A_Add 클래스입니다


여기서 중요한 것은 A_Add클래스는 배열의 덧셈연산을 나타내지만
실재 덧셈 연산을 하지 않습니다
다시 한번 언급하면 표현식 템플릿의 핵심 기술,  어떤 객체에 무슨 연산이 적용되었는지 기록하는 것 입니다
중요하니까 계속 반복할게요
여기 A_Add 클래스는 두 피연산자를 저장하고 덧셈 연산을 기록합니다
배열 참조 연산자에서 [] 덧셈을 기록하는 것입니다
두 피연산자를 저장했다가 A_Add 배열처럼 [] 접근할때 덧셈 연산을 한 결과를 제공합니다

그 다음은 곱셈을 나타내는 A_Mult 클래스 입니다 

곱셈도 마찬가지 입니다
다시 한번 언급하면 표현식 템플릿의 핵심 기술,  어떤 객체에
무슨 연산이 적용되었는지 기록하는 것 입니다
여기 A_Mult 클래스는 두 피연산자를 저장하고 곱셈 연산을 기록합니다
배열 참조 연산자에서 [] 곱셈을 기록하는 것입니다 
 다음을 스칼라를 나타내는 A_Scalar 클래스입니다

스칼라는 모든 인덱스에 대해 같은 값을 가지고 있는 배열과 같으므로 배열 참조 연산자 []에서는 모두 같은 값을 반환 합니다

도우미 클래스 A_Traits 클래스
피연산자를 정의할 때 이렇게 정의를 했습니다
typename A_Traits<OP1>::ExprRef op1;    // first operand
typename A_Traits<OP2>::ExprRef op2;    // second operand
최종 연산이 이루어지기 전까지 어떤 객체에 무슨 연산이 적용되었는지 기록하는 것
표현식 템플릿의 핵심입니다
대부분의 임시 노드들은 최상위 표현식과 관련이 있기에 전체 표현식 까지 살아 남아야 합니다
A_Scalar 노드는 예외 입니다
연산자 함수에 연결되며 전체 표현식의 계산이 종료 전에 사라집니다
(이게 왜그런지는 고민을 해보세요 그리고 저한테도 알려주세요 저도 잘 몰라서요) 
스칼리 피연산자는 값으로 복사돼야만 합니다

A_Traits 클래스 특질 클래스로 스칼라 처리를 하였습니다
A_Scalar, A_Add, A_Mult 이 세 클래스로 통해 표현식 템플릿을 표현합니다
앞으로 알아보기 힘들 정도로 템플릿의 향연을 보실 겁니다
그전에  짚고 넘어가겠습니다

A_Add는 두 피연산자를 저장하고 덧셈연산을 기록
A_Mult는 두 피연산자를 저장하고 곱셈연산을 기록
A_Scalar는 스칼라 값을 배열처럼 처리하는 것

템플릿 안에 템플릿이 가능하듯이 또는 곱셈 후에 덧셈, 덧셈 후에 곱셈... 연산후에 연산 하듯이 사용이 가능합니다
말을 복잡하게 썻네요 즉  A_Scalar, A_Add, A_Mult는 또 다른  A_Add, A_Mult의 피연산자로 가능합니다
그럴때는

A_Add 두 배열을 덧셈을 저장한 배열
A_Mult 두 배열을 곱셈을 저장한 배열 
A_Scalar 모두 같은 값을 같고 있는 배열
라고  판단하셔도 무관합니다
어짜피 시작을 배열연산에 발생하는 임시 변수의 비효율 때문에 템플릿 표현식을 언급했거든요 

너무 길어 졌네요
다음에는  실제 저장소를 제어하고 표현식 템플릿을 알고 있는 Array 형 클래스를 만들겠습니다
:

C++ Basic Concept, Storage Duration

Short Articles 2012. 2. 10. 12:21
Sotrage duration 이란 object 에 대해서 object 를 포함하고 있는 storage 의 최소 유효 생명 주기를
정의하는 속성 입니다. Storage duration 는 다음과 같은 것들이 있습니다.
  • Static storage duration
  • Thread storage duration
  • Automatic storage duration
  • Dynamic storage duration 

작성중...
 
 
:

C++ Template, Expression Templates #1

Short Articles 2012. 2. 8. 16:50
표현식 템플릿은 숫자 배열 클래스를 지원하기 위해 고안된 프로그램이 기술입니다
이번 아티클은 다음과 같이 진행합니다
1. 단순한 배열 클래스 구현하여 배열 연산에 대한 문제제기
2. 표현식 템플릿 구현
3. 표현식 템플릿을 아는 랩퍼클래스 구현
4. 실재 표현식 템플릿 사용해서 어떻게 동작하는 방법 추적


배열 클래스를 구현해서 이런 계산을 해봅시다
단순한 배열 클래스 입니다 

단순한 배열을 클래스로 구현하였습니다
산술연산자는 다음과 같이 구현합니다
자 배열 클래스와 연산자를 구현하였습니다
배열 계산을 해봅시다
코드상 배열을 클래스로 구현했다고 생각할 수 있습니다
그러나 이런 구현은 두가지 이유때문에 비효울적으로 알려져 있습니다
1. 연산자의 모든 응용은 적어도 하나의 임시 배열을 생성한다
2. 연산자의 모든 응용은 인자와 결과 배열에 대해 부가적으로 탐색한다 

산술 배열 라이브러리의 초기 구현에서는 사용자들이 계산 할당자(+=,*=) 사용해 임시배열을
줄이게 권유했다고 합니다


이런 연산자를 사용하면 다음과 같이 수정할 수 있네요
표기 방식이 이상하고 여전히 tmp 배열이 필요하고
루프로 인해  메모리 읽기 대략 6000회, 쓰기 대략 4000회 사용합니다

우리가 원하는 실질적인 계산은 바로 이것입니다

배열을 어떻게 효율적으로 연산을 할까요? 

'Short Articles' 카테고리의 다른 글

C++ Template, Expression Templates #2  (0) 2012.02.17
C++ Basic Concept, Storage Duration  (0) 2012.02.10
C++ Basic Concept, One definition rule (ODR)  (2) 2012.02.07
C++11, Lambda Expressions #2  (0) 2012.02.01
C++11, Lambda Expressions #1  (2) 2012.01.30
:

C++ Basic Concept, One definition rule (ODR)

Short Articles 2012. 2. 7. 14:57
(이 글은 C++ Templates the Complete Guide 부록의 내용을 바탕으로 작성되었습니다.)

C++ , ODR 에서 어떤 변수, 함수, 클래스 형, 열거 형 또는 템플릿도 번역 단위 내에서
딱 한 번만 정의 되어야 한다고 규정하고 있습니다.

+번역단위
번역 단위는 #define 매크로 치환등의 전처리 적용 이후 컴파일러에게 전달되는 코드의 덩어리를 지칭합니다.
컴파일러가 하나의 object 를 생성해내는데 참조하게 되는 코드 범위를 가리킨다고 볼 수 있고요.
따라서 파일 단위와는 차이가 있습니다. 

예를 하나 들어 보겠습니다.

a.h
a.cpp
main.cpp
이렇게 3개의 파일이 있다고 했을 때, 컴파일러는 a.cpp , main.cpp 두 소스 파일에 대해서 각각
a.obj , main.obj (g++ 이라면 a.o , main.o) 라는 오브젝트 파일을 생성합니다.
이 때 컴파일러에 전달되는 것은 '#include '로 포함된 파일의 내용이 추가된 코드 덩어리 입니다.

컴파일러는 이렇게 각 각의 코드 덩어리를 해석하기 때문에
foo() 함수의 정의는 a.obj 에 기록 되지만 main.obj 에는 foo() 함수의 구현을 모릅니다.
main.obj 는 단지 foo() 함수의 symbol 정보만을 들고 있습니다.
이것은 linking 과정에서 linker 에 의해 해석되어 구현 코드와 연결되게 됩니다.

ms윈도우에서 vs 기반으로 작업을 하시는 분들 중에 이런식의 컴파일/링킹 과정을 
분리해서 생각 하는 것에 생소한 분들이 좀 있는 것 같습니다. 
a.cpp 파일이 없더라도 컴파일 과정은 문제 없이 수행됩니다.

컴파일러가 cl.exe 라면 /c 옵션을 주어 컴파일 과정만을 수행할 수 있습니다.




아무튼 위에서의 foo() 함수 같은 경우에 external linkage 를 갖는다고 합니다. 반대로 같은 번역 단위에
존재할 경우에는 internal linkage 를 갖는다고 하죠.

다시 ODR 에 대한 이야기로 돌아가서..

+ ODR 에 따라 프로그램에서 단 한번만 정의 되어야하는 것에는 다음과 같은 것들이 있습니다.
    • 인라인이 아닌 함수/인라인이 아닌 멤버 함수
    • external linkage 를 갖는 정적 데이터 멤버
    • 인라인이 아닌 (함수 템플릿/멤버 함수 템플릿/클래스 템플릿 멤버) 가 export 와 함께 선언된 경우
    • 클래스 템플릿의 정적 데이터 멤버가 export 로 선언된 경우 
이 것들은 external linkage 를 갖는 사항들로 internal linkage 를 갖는 것들에 대해서는 적용되지 않습니다.
(번역단위 내에 선언된 static 변수,  anonymous namespace 등은 internal linkage 를 갖습니다.)

프로그램 내에서 어떤 실체에 대해 사용(명시적이든 묵시적이든 참조를 하는)이 있을 경우,
이 실체는 프로그램 내에 하나만 존재 해야 합니다. 
이 규칙에 대해 (sizeof , typeid) 사용이 좀 예외적일 수가 있는데
이 둘의 연산자의 일부로써 실체에 대한 참조가 일어날 경우 기본적으로는 실체가 사용되었다고 보지 않지만
typeid 연산자의 인자가 다형적 객체(상속, 가상 함수를 포함)를 가리키는 경우에는 사용되었다고 판단합니다.


+ 번역 단위 내에서 실체는 한 번 이상 정의 될 수 없습니다.
구조체/공용체를 포함한 클래스 타입은 번역 단위 내에서 다음과 같은 사용 사항들에 대해
정의돼 있어야 합니다.
  • 객체의 생성(포함관계 등으로 인한 간접 생성을 포함)
  • 데이터 멤버의 선언 
  • 객체에 대한 sizeof / typeid 연산자
  • 멤버에 대한 (명시적/암시적) 접근
  • 임의의 어떤 변환을 사용한 표현식 - 클래스 타입 상호간 변환
  • 암시적 형변환, static_cast, dynamic_cast 를 사용한 표현식 - (void*를 제외한)클래스 포인터(또는 참조) 상호간 변환
  • 값의 할당
  • (인자나 리턴 타입을 포함)함수 정의 또는 호출, 그러나 선언만의 경우 정의될 필요 없음.
 
이 규칙은 클래스 템플릿을 통해 생성되는 클래스 타입에 대해서도 적용됩니다.
즉, 클래스 타입을 인스턴싱 시키는 클래스 템플릿은 위와 같은 상황에 대해 정의되어 있어야 합니다.

인라인 함수들의 경우에는 인라인 함수가 사용되는 각각의 번역 단위에 정의가 있어야 합니다.
인라인은 코드 치환이므로 심볼만을 인식하고 있는 경우 코드 정의를 치환할 수 없습니다.
 
+ 교차 번역 단위에서의 동등성 제약
이 부분은 설명 생략하겠습니다.
참고적으로, MS 의 c++ 컴파일러인 cl.exe 는 다중 정의 자체를 허용 하지 않습니다.
관련해서 검색해보시고 테스트 해보실 분은 g++ 을 이용하여 테스트 해보시기 바랍니다.


 

'Short Articles' 카테고리의 다른 글

C++ Template, Expression Templates #2  (0) 2012.02.17
C++ Basic Concept, Storage Duration  (0) 2012.02.10
C++ Template, Expression Templates #1  (3) 2012.02.08
C++11, Lambda Expressions #2  (0) 2012.02.01
C++11, Lambda Expressions #1  (2) 2012.01.30
:

C++11, Lambda Expressions #2

Short Articles 2012. 2. 1. 18:52
앞 글에 이어 우선 lambda-declarator 부분을 잠시 보고, capture 와 관련된 부분들을 다시 살펴 보겠습니다.

+ lambda-declarator
lambda-declarator 는

(파라미터 선언 절) mutable exception-specification attribute-specification trailing-return-type


의 형식으로 작성되고,
이 중에서 (파라미터 선언 절) 외의 부분들은 필요에 따라 적어주면 되는 부분들입니다.
lambda-declarator 에 포함된 사항들은 C++ lambda 의 새롭게 추가된 사항이 아닌 기존 C++ 에 이미 정의된 사항들 이므로 자세한 내용은 생략 하겠습니다.

간단한 예시 코드,
이 람다식이 포함하는 closure object 는 ecode 변수를 copy 로 capture 합니다.
mutable 키워드는 capture 를 compound-statement 내에서 수정 가능함을 나타내고,
뒤따라오는 throw(int) 는 int type 의 예외를 던진다는 것을 알립니다. ->int 는 compound-statement 의
리턴 타입이 int 형임을 지정합니다.

[=](char const *s) throw(int) ->int ...  와 같이 mutable 키워드를 지정하지 않는 경우
closure 에서 public 으로 선언(정의)되는 function call operator 는 기본 const 입니다.

다시 capture 관련 이야기로 돌아가서...

local-lambda expression
block scope 바로 아래 범위에 닿아 있는 람다 표현식을 local lambda expression 이라고 합니다.
local lambda expression 의 영향 범위는 자신의 한겹 위쪽 블럭과 아래쪽으로 포함되는 영역 입니다.
(함수와 함수의 파라미터를 포함해서..)
이 유효 범위 사이에는 또다른 람다 표현식이 끼어들 수도 있습니다.


위 코드에서 람다 표현식의 범위는 PrintPoint 함수의 블럭과 표현식이 포함하고 있는 출력 코드 부분입니다.
이 경우  name 은 local lambda expression 의 유효 범위 밖에 있기 때문에 에러가 납니다.
이 때에는 명시적으로 this 를 capture 하거나 PrintPoint 의 지역 변수로 const char* 하나를 선언해서
name 의 값을 받은 후 이것을 명시적으로 capture 하는 식으로 에러를 피할 수 있습니다.

lambda 표현식의 capture-list 에 대한 name look up 은 일반적인 unqualified name look up
(:: , std:: , boost::  등으로 특정 짓지 않은 entitiy 에 대한 name look up) 에 따릅니다. 
각각의 룩업은 local lambda expression 의 유효 범위 내에서 automatic storage duration 으로
선언된 변수를 찾습니다.

automatic storage duration :
자동으로 생명주기가 관리되는 것들을 말한다. variable with automatic storage duration 이라 하면,
C++ 에서는 주로 stack 에 생성되는 지역 변수등을 일컫는다.

 
ODR & lambda-expression

(참고. cl.exe 는 아직 implicitly capture 를 제대로 지원하지 못 하고 있습니다. )

람다 표현식이 capture-default 를 포함하고 표현식의 odr 사용이 this 나 automatic storage duration
변수 그리고 odr이 사용된 entity 가 명시적으로 캡처(explicitly capture)되지 않았다면,
odr이 사용된 entity 는 임시적으로 캡처(implicitly capture) 되었다고 합니다.

다음은 표준 문서에 나오는 implicitly capture 에 대한 예제 코드 입니다.
코드 작성과 테스트는 eclipse with g++4.6.2 로 했습니다.
이 코드를 컴파일하면 다음과 같은 에러를 보게 됩니다.

../src/hello.cpp: In lambda function:

../src/hello.cpp:38:18: error: 'j' is not captured

../src/hello.cpp: In lambda function:

../src/hello.cpp:39:14: error: 'n' is not captured

../src/hello.cpp:43:11: error: 'i' is not captured

../src/hello.cpp: In lambda function:

../src/hello.cpp:45:5: error: unable to deduce 'auto' from '<expression error>'


N 과 M 은 m1 에 의해서 implicitly capture 되었기 때문에 괜찮지만,
j 는 m3 에 의해서 캡처 되지 않았고 n 은 m4 에 의해서는 implicitly capture 되지만
m4 를 둘러싸고 있는 m3 가 n 을 캡처 하지 않았기 때문에 에러입니다.
그리고 i 는 유효범위 밖에 있으므로 역시 에러가 나고 있습니다.

다음과 같이 명시적으로 n, j 를 캡처하고 x += i; 를 주석 처리 하면
에러없이 컴파일 되는 것을 확인할 수 있습니다.
 
기본 인자로 lambda-expression 이 올 때, 어떤 entity 도 명시적이든 암시적이든 캡처할 수 없습니다.
이 부분은 개인적으로 그다지 중요한 부분은 아니라고 생각하는데,
아무튼 람다 표현식이 기본인자로 쓰일 경우 

void foo(int = ( []{ return 0;}() ));

처럼 상수식이 반환되는 경우는 ok 지만 어떤 것이든 캡처를 하는 경우는 에러입니다.
별다른 내용은 없습니다.

entity - captured by copy
entity 가 implicitly capture 되었고 capture default 가 '=' 이거나
entity 가 explicitly capture 되었고 '&' 를 포함하지 않으면,
이 entity 는 copy 로 캡처 되었다고 합니다.

by copy 로 capture 캡처된 entity 는 closure 의 non-static unnamed 데이터 멤버로 선언됩니다.
이 때, 선언되는 순서에 정해진 규칙은 없습니다. entity 가 객체나 그 밖의 타입에 대한 참조가
아닐 경우 이렇게 되며, int &p = value; 에서의 p 같은 참조 변수는 captured by copy 되지 않습니다.

entity - captured by reference 
명시적으로 또는 암시적으로 캡처되지만 copy 로 캡처되지 않는 entity 는 reference 로 캡처됩니다.

nested lambda expressions 에서 한 표현식을 다른 표현식이 바로 둘러 싸는 경우
표현식 m1이 m2 를 둘러 싸고 있다고 할 때,
m1 이 by copy 로 캡처한 entity 는 m1 의 closure 타입의 non-static data member 에 대응되는 것으로
m2 에 캡처됩니다. 그리고 m1 이 by reference 로 캡처한 entity 는 m1 이 캡처한 것과 동일한 entity 로
m2 가 캡처합니다.
이 코드의 결과 값은 다음과 같습니다.

123234



 
람다 표현식에 대한 글은 여기서 이만 줄이겠습니다.
표준 문서에 몇 가지 추가적인 사항에 대한 설명이 나오므로 더 자세한 사항을 원하시는 분은
표준 명세를 찾아보시기 바랍니다.
 
:

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 의 값은 변경한대로 전달이 되었습니다.

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

 

: