[C++ 11] 람다(lambda) 식
람다(lambda) 식
간단하게 말하면 이름없는 함수를 의미한다.
함수를 호출하는 방법은 C++에서는 다양한 방법이 존재한다.
1. 일반 전역 함수, 멤버 함수등
2. 함수 객체 ( 클래스 () 연산자 오버로딩 )
3. 함수 포인터사용
여기서 벡터 컨테이너의 요소를 정렬하는 코드를 보면.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
bool Compare(std::pair<Test, int> lhs, std::pair<Test, int> rhs)
{
return lhs.first.GetValue() > rhs.first.GetValue(); // 내림차순 정렬
}
int main()
{
srand(time(nullptr));
std::vector<std::pair<Test, int>> vc;
vc.reserve(100);
for (int i = 0; i < 100; ++i)
{
int random = (rand() % 300) + 1;
Test t(random);
vc.push_back(std::make_pair(t, i));
}
std::sort(vc.begin(), vc.end(),Compare);
return 0;
}
|
cs |
위와 같이 내림차순 정렬이 가능하다. (혹은 함수객체를 사용하는 방법도 존재한다)
그런데 저 벡터의 요소를 저기서 단 한번만 정렬을 하고, 단순히 객체의 멤버 하나로 정렬을 하는건데 굳이 함수 객체나 함수를 만들어서 정렬을 하는건 코드 라인 수만 잡아먹는다는 생각이 든다.
이럴 때 사용할 수 있는게 람다 식
1
2
3
4
5
6
7
|
std::sort(
vc.begin(), vc.end(),
[](std::pair<Test, int> lhs, std::pair<Test, int> rhs) -> bool //반환형 선언 ->는 선택사항.(자동추론)
{
return lhs.first.GetValue() > rhs.first.GetValue(); // 내림차순 정렬
}
);
|
cs |
정렬을 위한 함수나 함수 객체를 따로 만들어 줄 필요없이 이름 없는 함수 람다 식을 사용하면 더욱 간결하게 처리할 수 있다.
더욱 장점인 점은 함수의 인라인(inline,함수호출 과정이 존재하지 않음)화 되기에 함수 호출의 미세한 오버헤드도 줄일 수 있다.
-> 함수 포인터를 사용하는 경우 인라인화 되지 않는다. (inline함수는 매크로 함수처럼 컴파일 타임에 치환)
즉 람다 식은 이름 없는 함수이며 인라인으로 정의와 호출을 한번에 하는 함수.
-> 이름 없는 함수 객체
--> 메모리 상에 임시적으로 객체가 생성된다. (함수 객체처럼 행동)
-> 내포되는 함수
이제 람다 식의 문법을 자세히 살펴보면
매개변수 목록과 반환 형은 보기만 해도 알 수 있다.
캡처블록과 지정자만 보자.
캡처블록에 람다 함수안이 아닌 외부 변수를 넣는다면 이를 캡쳐하여 해당 외부 변수를 람다 내부에서 이용할 수 있게 된다.
1
2
3
4
5
6
7
8
9
|
int main()
{
int temp = 50;
auto func = [&temp](int value) { temp += value; std::cout << temp; };
func(100);
return 0;
}
|
cs |
실행 결과는
람다 함수도 함수이기에 외부 변수에 대한 정보가 없다면 접근이 불가능한게 당연하다.
먼저 캡처목록의 종류를 살펴본다.
1. [] - 빈 캡처목록.
2. [=] - 외부의 모든 변수들을 값으로 가져온다. (Call-by-value)
3. [&] - 외부의 모든 변수들을 레퍼런스로 가져온다. (Call-by-reference)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
int main()
{
int temp = 50;
int temp2 = 100;
//Call-by-Value
auto Max = [=]() -> int { return (temp > temp2) ? temp : temp2; };
//Call-by-reference
auto Min = [&]() -> int {return (temp > temp2) ? temp2 : temp; };
std::cout << "Max 값은 " << Max() << std::endl;
std::cout << "Min 값은 " << Min() << std::endl;
return 0;
}
|
cs |
참고로 값에의한 캡쳐는 값의 수정이 불가능하다.
=,&는 외부의 모든 변수에 접근할 수 있다.
그러길 원치 않고 명시적으로 외부 변수들 중 원하는 변수만으로도 캡쳐가 가능하다.
혹은 외부 변수를 모두 값에 의한 캡쳐를 하면서 특정 변수만 참조에 의한 캡쳐도 가능하다.
1
2
|
//가능!
auto test = [=, &temp]() { temp = 100; std::cout << temp2 << temp3; };
|
cs |
마지막으로 지정자.
해당 하는 곳에 mutable이라는 키워드를 넣을 수 있다.
의미하는바는 간단한데 값에 의한 캡쳐 외부 변수들을 수정할 수 있게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
int main()
{
int temp = 50;
int temp2 = 100;
int temp3 = 25;
std::cout << "람다 호출 전 값 \n" << temp << "\n" << temp2 << "\n" << temp3 << std::endl;
//값에 의한 캡쳐(Call-by-Value)
auto test = [=]() mutable
{
temp = 100; // 수정 가능!
temp2 = 200; // 수정 가능!
temp3 = 300; // 수정 가능!
std::cout << "람다 호출 중 값 \n" << temp << "\n" << temp2 << "\n" << temp3 << std::endl;
};
test();
std::cout << "람다 호출 후 값 \n" << temp << "\n" << temp2 << "\n" << temp3 << std::endl;
return 0;
}
|
cs |
당연하지만 값에 의한 호출이기에 람다 내부에서 값을 수정해도 실제 값은 변경되지 않는다.
람다 식은 사용하기 편리하다는 장점이 존재하지만, 디버깅이 힘들고 함수의 재사용성이 낮다는 단점도 존재한다.
-> 기본적으로는 이름 있는 함수 사용.
-> 한 줄짜리 함수 혹은 단 한번 쓰이고 말 함수(위의 예제와 같은 케이스 -> 정렬 함수)