본문 바로가기

Programming/CPP11&14

[C++11] 이름 없는 함수, 람다(Lambda)(2)

반응형

이름 없는 함수, 람다(Lambda)(1) 링크는 다음과 같습니다.

2014/12/11 - [Programming/C++11&14] - [C++11] 이름 없는 함수, 람다(Lambda)(1)

람다에 대한 설명에 앞서서 람다의 구조에 대한 이미지를 다시 보도록 하겠습니다.

1. 캡처(Capture)

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

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

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

#include <iostream>
using namespace std;

void main()
{
	int a = 0, b = 1;
	[]()
	{
		// cout << a << endl; // Error
	}();
}

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

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

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

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

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

mutable 키워드에서 다시 설명하겠지만 [=]로 캡처했을 때 주의 하실 점은

mutable 키워드를 사용하지 않으면 값의 변경이 되지 않습니다.

mutable 키워드를 사용해서 값을 변경해도 call by value 형태기 때문에 원래 a는 값이 변하지 않습니다.

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

#include <iostream>
using namespace std;

void main()
{
	int a = 0, b = 1, c = 2;
	[&a, b]()
	{
		cout << a << ", " << b << endl;
		// cout << c << endl; // Error
	}();
}

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)를 사용할 수 없다.

디폴트 인자를 사용하려고 하면 컴파일러가 에러를 표시합니다.

3. mutable

C++ 클래스의 const 멤버 함수는 객체의 값을 수정할 수 없는 함수입니다.

예외적으로 const 멤버 함수에서도 값을 수정할 수 있게 하기 위해서 변수를 mutable로 선언하면 됩니다.

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

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

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

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

#include <iostream>
using namespace std;

void main()
{
	int a = 0, b = 1, c = 2;
	[=]()
	{
		// a = 3; // Error
	}();
}

Visual Studio 기준으로 다음과 같은 에러가 출력됩니다.

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

다음과 같이 수정하면 정상적으로 컴파일이 됩니다.

#include <iostream>
using namespace std;

void main()
{
	int a = 0, b = 1, c = 2;
	[=]() mutable
	{
		a = 3;
	}();
}


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

4. throw

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

함수의 뒤에 throw(int)와 같은 형태로 타입을 지정해주면 함수가 int 타입으로 예외를 전달할 것이다라고 알려주는

일종의 주석의 역할을 합니다.

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

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

다음과 같은 형태로 사용할 수 있습니다.

#include <iostream>
using namespace std;

void main()
{
	int a = 0, b = 1, c = 2;
	try
	{
		[]() mutable throw(int)
		{
			throw 5;
		}();
	}
	catch (int i)
	{
		cout << "Error No. " << i << endl;
	}
}

5. Return Type

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

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

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

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

람다는 람다를 리턴하거나 람다를 람다의 인자로 넣을 수 있는데 이러한 속성을 Higher-order Function이라고 합니다.

#include <iostream>
using namespace std;

void main()
{
	int a = 0, b = 1, c = 2;
	int nVal = [=]() mutable throw() -> int
	{
		return 1000;
	}();

	cout << nVal << endl;
}

int 형을 반환한다는 것을 명시했고 1000을 리턴했습니다.

6. Body

함수의 캡처한 변수를 사용할 수 있는 점을 제외하면 기존의 함수 구현과 동일하게 사용이 가능합니다.

람다는 함수 포인터에 비해 더 많은 기능을 제공할 수 있고, 코드 상에서도 함께 해야할 코드를 묶어주는 것이 가능합니다.

기존의 Function Object를 사용하면 알고리즘과 알고리즘을 사용하는 부분이 분리되지만 람다는 한 곳에 둘 수 있습니다.

C++11에 람다가 추가되어 좀 더 효율적인 코딩이 가능해졌습니다.

반응형

'Programming > CPP11&14' 카테고리의 다른 글

[C++11] RValue Reference(2)  (0) 2014.12.17
[C++11] RValue Reference(1)  (0) 2014.12.14
[C++11] 이름 없는 함수, 람다(Lambda)(1)  (0) 2014.12.11
[C++11] Range-Based For Loop  (0) 2014.12.11
[C++11] auto 키워드  (0) 2014.12.10