나만의 작은 도서관
[C++][Callback] #4. 람다 표현식(Lambda Expression) 본문

람다(Lambda) 표현식(C++11)
// 기본 형태 : [캡처](매개변수들)->리턴값{함수내용};
// 리턴값은 생략가능
[](Item& item) {return item._rarity == Rarity::UNIQUE;};
// 람다로 인해 만들어진 실행시점 객체 = 클로저(closure)
auto isUniqueLambda = [](Item& item) {return item._rarity == Rarity::UNIQUE; }
- 람다 표현식은 함수 객체(Functor)를 빠르게 만드는 문법으로, 반환값은 람다 표현식이 정의될 때 생성된 익명 클래스의 객체이다. (이하 람다 표현식을 “람다”라고 지칭)
- 당연하게도 람다의 리턴값인 익명 함수 객체는 Callable 한 개체 중 하나이다.
- 리턴값인 익명 함수 객체는 런타임에서 이름은 없지만 메모리상에 임시적으로 존재하는 "클로져(Closure) 객체"이다.
- 람다의 추가로 새로운 기능이 생긴 건 아니지만, 기존의 functor를 사용하기 위한 지지부지한 과정(호출 연산자 오버로딩 구조체 정의 등) 없이 간결하고 직관적인 functor 코드 작성이 가능해졌다.
- 람다를 정의한 문장 뒤에 호출 연산자 ()를 붙여 람다를 바로 호출할 수 있다.
Lambda_Expression() // 람다의 반환값은 익명 "함수 객체".
// exmaple
[](int v) { cout << v << "*6=" << v * 6 << endl; }(7); // 실행 결과: 7*6=42
람다의 구성

- 람다는 위 그림과 같이 개시자(introducer), 인자(parameters), 리턴 타입, 몸통(statement) 총 4개의 부분으로 구성되어 있다.
- []는 개시자 캡처로, 개시자 캡처 안에 외부 변수를 써넣는다면 람다 함수가 이를 캡처해서 캡처한 변수를 람다 내부에서 사용할 수 있게 된다.
- ()는 람다를 호출할 때 받을 인자들을 정의한다. 함수의 매개변수를 정의하듯 적어준다. (인자가 없다면 ()는 생략이 가능하다.)
- → 는 람다의 리턴 타입을 정의한다. 함수의 리턴 타입을 정의하듯 적어주면 된다. (리턴 타입은 생략이 가능하다.)
- {}는 람다의 몸통으로, 함수의 정의 부분에 해당한다.
[ ] 캡처(Capture)
- 캡처(Capture)는 람다 외부에 정의된 변수들을 람다 내부에서 사용할 수 있도록 하는 기능이다. (functor 내부에 변수를 저장하는 개념과 유사)
- 캡처는 매개변수 리스트가 고정되어 있거나 변경하고 싶지 않은 상황에서 유용하다.
- 예를 들어, STL에서 제공하는 for_each, fill, transform 등의 함수에 functor를 넘겨주는 경우, functor는 이미 정의된 호출 규약(매개변수의 개수 및 타입)과 일치하는 매개변수 리스트를 가져야만 넘겨줄 수 있다. 그런데 이러한 상황에서 더 많은 변수들을 넘겨주고 싶은 상황이 생길 수 있다. 이때 람다의 캡처를 사용하면 람다 내부에 더 많은 변수를 넘겨줄 수 있다!
int sum = 0;
for_each(v1.begin(), v1.end(), [&sum](int i) {sum += i}/* 캡처를 이용해 sum을 넘겨줌 */);
캡처 모드 종류
- 캡처를 통해 람다 내부로 변수를 넘겨줄 때 변수마다 캡처 모드를 지정할 수 있다.
- 캡처 모드는 "값 복사 방식(=)"과 "참조 방식(&)"이 있으며, 변수 앞에 ‘&’ 연산자를 붙이는 것으로 캡처 모드를 "참조 방식(&)"으로 변경할 수 있다. (기본 캡처 모드는 "값 복사 방식(=)")
- 만약 명시적 지정 없이 람다 내에서 사용한 모든 외부 변수들을 동일한 캡처 모드로 지정하고 싶다면 ‘[=]’ 또는 ‘[&]’를 사용하면 된다.
람다 캡처 모드 예시
- [ ]: 캡처 안 함(외부 변수 사용 X)
- [&]: 람다 내부에서 사용하는 모든 외부 변수들을 “레퍼런스”로 가져온다. (call-by-reference)
- [=]: 람다 내부에서 사용하는 모든 외부 변수들을 “값”으로 가져온다. (call-by-value)
- [&a, x]: 각 외부 변수들을 지정한 캡처 모드로 가져온다. ('='은 생략 가능)
캡처에 대한 여러 가지
기본 캡처 모드 사용은 지양해야 한다.
- C++에서는 기본 캡처 모드의 사용은 지양하라고 한다. 왜냐하면 기본 캡처 모드는 멤버 변수를 캡처할 때 암묵적으로 this 포인터를 캡처하기 때문에 원하지 않는 복사 또는 참조가 발생할 수 있기 때문이다.
- 따라서 람다를 사용할 경우, 되도록이면 캡처하고자 하는 외부 변수에 대해 명시적으로 캡처 모드를 지정하여 사용하는 것이 바람직하다.
람다는 언제 캡처되는가?
- 캡처는 람다의 임시 함수 객체가 생성될 때의 변수를 캡처한다.
int v = 42;
auto func = [=] { cout << v << endl; }; // 42를 캡처
v = 8;
func(); // 42
캡처된 값은 자동으로 const 속성을 가진다.
- 캡처된 값은 내부에서 const가 자동으로 부여되기 때문에 값을 수정할 수 없다. (매개변수와는 상관없음)
- 만약 캡처한 변수의 값 수정하고 싶다면 람다에 mutable을 추가해야 한다.
int i = 10;
auto two_i = [=]() /*mutable*/ -> int {
i *= 2; // 컴파일 에러: 식이 수정할 수 있는 lvalue여야 합니다.
return i;
};
- 멤버 변수를 캡처하는 경우 [=]는 암묵적으로 멤버 변수가 아닌 this 포인터를 캡처하기 때문에 const 속성은 this 포인터에 부여된다. 따라서 아래와 같은 코드에선 mutable 없이도 멤버 변수의 값을 수정할 수 있다.
class foo
{
public:
foo() : i(0) {}
void modifyMember(){
[=]() { i = 8; }(); // 람다 생성과 동시에 호출
}
int i = 99;
};
foo f;
f.modifyMember();
cout << "f.i : " << f.i; // 실행 결과: f.i : 8
캡처의 범위
- 람다는 접근 가능한 변수만 캡처가 가능하다. 아래 코드와 같이 외부 변수를 캡처하지 않은 외부 람다에 싸인 다면, 내부 람다는 외부 람다 밖에 있는 변수에 접근할 수 없다.
int i = 8;
auto outerLambda = []()
{
int j = 2;
// 컴파일 에러: 바깥쪽 람다가 암시적 캡처를 허용하지 않으므로
// 이 바깥쪽 함수의 지역 변수를 이 람다 본문에서 참조할 수 없습니다.
auto innerLambda = [=] { cout << i/*접근 X*/ / j; };
innerLambda();
};
outerLambda();
참고 자료
'C++ > 문법 및 메소드(STL)' 카테고리의 다른 글
[C++] extern 키워드와 전역 변수 (0) | 2025.04.09 |
---|---|
[C++] 스마트 포인터(Smart Pointer) (0) | 2025.04.06 |
[C++][Callback] #3. 함수 객체(functor) (0) | 2025.04.03 |
[C++][Callback] #2. 함수 포인터(Function Pointer) (0) | 2025.04.02 |
[C++][Callback] #1. 콜백 함수와 Callable, 그리고 std::function (0) | 2025.04.02 |