C++11표준 부터 도입된 람다는 람다 대수 (lambda calculus) 에서 유래한다.
1930년대에 수학에 도입된 람다 대수와 람다식은, 1960년대 중반 프로그래밍 언어에 익명의 함수 형태로 도입되었다.
람다 대수에서 람다식 (lambda expression) 은 수학의 함수를 단순하게 표현하는 방법이다.
아래와 같이 수학에서 이름 없는 익명 함수 (anonymous function)를 람다식 (혹은 람다 함수) 이라고 부른다.
f(x, y) = x + y
(x, y) -> x + y
그리고
((x,y)->x+y)(2,3)
= 2+3
= 5
🔖
[ ] ( ) -> 리턴타입 { /* 함수 코드 작성 */ }
[ ]
: 캡쳐 리스트
( )
: 매개변수 리스트 // 보통 함수의 매개 변수 리스트와 같다.
-> 리턴타입
: 생략 가능
{ /*함수 코드 작성*/ }
: 함수 바디 // 람다식이 호출될 때 실행되는 코드로서, 함수를 작성하는 방법과 동일하다.
캡쳐 리스트는 이름에서도 느껴지듯, 람다식의 외부에 선언된 변수 (지역변수, 전역변수) 목록으로,
람다식에서 사용하고자 할 때 나열하며 다음과 같이 여러 표현들이 있다.
표현 | 의미 |
---|---|
[x] | 변수 x의 값 활용 |
[=] | 모든 변수의 값 활용 |
[&x] | 참조 변수 x 활용 |
[&] | 모든 참조 변수 활용 |
람다식의 형식은 컴파일러에만 알려져 있으므로, 개발자가 람다식을 저장하는 변수를 직접 선언할 수 없다.
auto를 이용하면 람다식을 저장하는 변수를 쉽게 선언할 수 있다.
int main(){
vector<int> v = {1,2,3,4,5};
for_each(v.begin(), v.end(), [](int n){cout << n << " ";});
}
C++프로그램 작성 시 람다가 꼭 필요한 기능은 아니다.
다만 간단하고 짧게 최적화된 코드를 작성하는데 도움이 된다.
특별히 람다가 유용한 경우는 다음과 같다.
람다는 보통 익명 함수
라고 배우는데, 사실 상태까지 저장 가능한 함수 객체라고 볼 수 있습니다. 알고리즘 라이브러리랑 매우 궁합이 좋은데, 함수 자체를 1회성으로 만들어서 빠르게빠르게 넘길 수 있기 때문에 참 유용하다. 그래서 앞으로 람다를 권장하는 편이다.
[](){}
먼저 이거부터 시작해보자구요
람다의 장점: 1회 용도로 사용할 땐 굳이 함수의 이름을 지어주지 않아줘도 됨.
{
// 그냥 해보기
struct IsUniqueItem
{
bool operator()(Item& item)
{
return item._rarity == Rarity::Unique;
}
};
std::find_if(v.begin(), v.end(), IsUniqueItem());
// ========================================
// 람다로 바로 해보기
std::find_if(v.begin(), v.end(), [](Item& item){return item._rarity == Rarity::Unique})
/*
[](){}연습:
[](Item& item)
{
return item._rarity == Rarity::Unique;
}
*/
// ========================================
// 람다 전 한 번 거치기
auto isUniqueLambda = [](Item& item){return item._rarity == Rarity::Unique;};
std::find_if(v.begin(), v.end(), isUniqueLambda);
}
반환 타입을 명시적으로 지정할 수 있다.
아래의 예시에선 -> int
만 추가하여 반환 타입을 int
로 지정했다.
이것은 옵션이고, auto
로 형식 연역 추론이 가능하다 !!
auto I = [](Item& item) -> int { return item._rarity == Rarity::Unique; };
capture 라는 기능을 이용해보자.
🔖 람다의 캡쳐 기능은 외부 변수를 람다식 내부에서 사용하거나 변경할 수 있도록 하는 기능이다. 이는 람다식을 보다 유연하게 만들어주고, 함수 객체의 상태를 유지하면서 해당 상태를 람다식에서 활용할 수 있도록 한다.
캡쳐모드에는 = 복사 방식
과 & 참조 방식
이 있다.
[=](Item& item){return item._itemId == wantedId;};
// 모든 아이들을 복사방식으로 넘기겠다.
[&](Item& item){return item._itemId == wantedId;};
// 모든 아이들을 참조방식으로 넘기겠다
그리고 캡쳐모드가 적용될 변수를 명시적으로 지정할 수 있다.
단일 변수마다 캡처 모드를 지정할 수 있다.
[wantedId](Item& item){return item._itemId == wantedId;};
// wantedId 를 복사해서 넘겨주겠다
[&wantedId](Item& item){return item._itemId == wantedId;};
// wantedId 를 참조값으로 받아주겠다
참조방식은 내부적으로는 아래와 똑같이 기능한다. 람다가 굉장히 우월하다는 것..
struct IsWantedItem
{
IsWantedItem(int& wantedId) : _wantedId(wantedId) { }
bool operator()(Item& item)
{
return item._itemId == _wantedId;
}
int& _wantedId;
};
함수 객체에 대한 보충 설명은 여기를 참고하자!
잘못 사용하면 피를 볼 수 있다. 완전 안전하지는 않다.
어떻게 하면 위험할까?
파기된 주소값을 사용할 수도 있게 되는 문제. 사실 람다의 탓이 아닌 것이다!
std::vector<int> numbers = {4, 2, 7, 1, 9};
std::sort(numbers.begin(), numbers.end(), [](int a, int b){
return a < b;
});
std::vector<int> numbers = {5, 2, 7, 1, 9};
auto it = std::find_it(numbers.begin(), numbers.end(), [](int num) {
return num 5 2 == 0;
}};
std::vector<int> numbers = {5, 2, 7, 1, 9};
std::for_each(numbers.begin(), numbers.end(), [](int num){
std::cout << num << " ";
});
std::vector<int> numbers = {5, 2, 7, 1, 9};
numbers.erase(std::remove_if(numbers.begin(), numbers.end(), [](int num) {return num % 2 == 0;}), numbers.end());
std::vector<int> numbers = {5, 2, 7, 1, 9};
std::vector<int> doubledNumbers;
std::transform( numbers.begin(),
numbers.end(),
std::back_inserter(doubledNumbers),
[](int num){
return num*2;
});
int multoplier = 2;
auto multiply = [multiplier](int num) {
return num * multiplier;
};
mutable
키워드는 람다식 내부의 상태를 변경하는 것을 허용하는 기능으로, 캡쳐된 변수의 값을 변경할 수 있게 해준다.int count = 0;
auto increment = [count]() mutable {
count ++;
};
increment();
int count = 0;
auto increment = [&count]() mutable {
count++
};
increment();
int x = 5;
int y = 10;
auto printSum = [x, &y]() {
std::cout << x + y << std::endl;
};
int x = 5;
int y = 10;
auto printProduct = [&x, y](){
std::cout << x * y << std::endl;
};