람다 함수란 무엇인가?
람다 함수(Lambda Function)는 메인 프로그램의 범위(scope) 밖에 별도로 구현할 필요 없이, 코드 내에서 곧바로 정의해 사용할 수 있는 인라인(inline) 함수입니다.
람다는 변수에 저장되는 값처럼 취급될 수도 있으며, 함수처럼 호출 가능한 객체, 즉 펑터(Functor)라고도 불립니다.
컴파일러가 람다 함수의 정의를 만나면 일반적으로 해당 람다를 위한 고유한 객체(custom object)를 생성합니다.
람다 함수는 일반 함수보다 더 많은 기능을 제공합니다. 대표적으로, 사용 중인 변수를 가져올 수 있는 캡처(capture) 방식이 있습니다. 이때 캡처된 변수는 객체의 멤버처럼 취급됩니다.
때때로 람다 함수는 "함수 객체(function object)"라고도 불리며, 자체적인 스코프를 가지고 일반 함수의 매개변수로 전달될 수 있습니다. 또한 람다는 자신만의 생명주기(lifetime)를 갖습니다.
람다 표현식의 구성 요소
- [ ] – 캡처(Capture)
- ( ) – 매개변수(Parameters, 선택 사항)
- -> – 반환 값(Return Value, 선택 사항)
- {...} – 함수 본문(Function Body)
람다의 문법(Syntax)
[ ](int a) -> int { return a - 1; };캡처(Capture)의 두 가지 방식
캡처(Capture)는 람다 함수가 특정 스코프 또는 중첩된 블록 내에서 사용 가능한 변수에 접근할 수 있도록 허용해 주는 절(clause)입니다.
사용 가능한 변수의 값을 캡처하는 방법은 크게 두 가지입니다.
1. 이름으로 캡처하기 (값 캡처)
객체를 이름으로 캡처하면 람다 함수 내부에 그 변수의 지역 복사본(local copy)이 생성됩니다.
다음 예제를 통해 이 개념을 이해할 수 있습니다.
int main(){
set s;
// set에 요소 추가
int i = 20;
for_each(s.begin(), s.end(), [i](T& elem){
cout << elem.getVal() * i << endl;
}
}위 예제에서는 람다 함수의 지역 복사본을 생성하여 값을 캡처합니다. 따라서 람다 내부에서 변수 i의 값을 변경하더라도 원래 변수에는 아무런 영향을 주지 않습니다.
2. 참조(Reference)로 캡처하기
참조로 객체를 캡처하면 람다 함수의 컨텍스트(context)를 직접 조작하게 됩니다. 따라서 함수 객체 또는 람다 함수가 캡처한 값은 변경될 수 있습니다.
다음 예제를 살펴보겠습니다.
int main(){
set s;
// set에 요소 추가
int result = 0;
for_each(s.begin(), s.end(), [&result](T& elem){ result += elem.getVal(); });
cout << result << endl;
}위 예제에서 result는 참조(&)로 캡처되었기 때문에, 람다 내부에서 발생한 모든 변경 사항이 원래 변수 result에 그대로 반영됩니다.
멤버 함수 내부에서의 람다 사용
람다 함수는 어떤 일반 함수 안에서도 매개변수 형태로 사용할 수 있습니다. 특히 클래스의 멤버 함수 내에서는 this 포인터를 캡처하여 클래스의 멤버 변수에 접근할 수 있습니다.
class func{
public:
func(set<T>s): s1(s){}
void func(){
remove_if(s1.begin(), s1.end(),
[this](int i) -> bool { return (i < level); });
}
private:
set<T>s1;
int level;
};위 예제에서 [this] 캡처를 통해 람다 내부에서 클래스의 멤버 변수 level에 접근할 수 있습니다. 이처럼 람다는 for_each, remove_if 같은 STL 알고리즘과 함께 사용될 때 특히 강력한 효율성을 발휘합니다.
마무리
람다 함수는 C++11부터 도입된 기능으로, 코드의 간결성과 가독성을 크게 향상시켜 줍니다. 콜백 처리, STL 알고리즘, 비동기 프로그래밍 등 다양한 상황에서 활용되므로 현대 C++ 개발자라면 반드시 익혀야 할 핵심 개념입니다.