[C++] lambda

seunghyun·2023년 6월 28일
0
post-custom-banner


이미지 출처

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

C++ 에서 람다식 선언

🔖 [ ] ( ) -> 리턴타입 { /* 함수 코드 작성 */ }

[ ] : 캡쳐 리스트
( ) : 매개변수 리스트 // 보통 함수의 매개 변수 리스트와 같다.
-> 리턴타입 : 생략 가능
{ /*함수 코드 작성*/ } : 함수 바디 // 람다식이 호출될 때 실행되는 코드로서, 함수를 작성하는 방법과 동일하다.

캡쳐 리스트

캡쳐 리스트는 이름에서도 느껴지듯, 람다식의 외부에 선언된 변수 (지역변수, 전역변수) 목록으로,
람다식에서 사용하고자 할 때 나열하며 다음과 같이 여러 표현들이 있다.

표현의미
[x]변수 x의 값 활용
[=]모든 변수의 값 활용
[&x]참조 변수 x 활용
[&]모든 참조 변수 활용

auto 로 람다식 저장 및 호출

람다식의 형식은 컴파일러에만 알려져 있으므로, 개발자가 람다식을 저장하는 변수를 직접 선언할 수 없다.
auto를 이용하면 람다식을 저장하는 변수를 쉽게 선언할 수 있다.

사용 예시

int main(){
	vector<int> v = {1,2,3,4,5};
	for_each(v.begin(), v.end(), [](int n){cout << n << " ";});
}

C++에서 람다식의 의미

C++프로그램 작성 시 람다가 꼭 필요한 기능은 아니다.
다만 간단하고 짧게 최적화된 코드를 작성하는데 도움이 된다.
특별히 람다가 유용한 경우는 다음과 같다.

  • 한 번만 호출하고 재사용하지 않기 때문에 함수에 이름을 붙일 필요가 없는 경우
  • STL 알고리즘 함수의 매개 변수에 연산 코드를 넘기는 경우, 연산 코드를 익명의 람다식으로 작성

활용 예시

람다는 보통 익명 함수라고 배우는데, 사실 상태까지 저장 가능한 함수 객체라고 볼 수 있습니다. 알고리즘 라이브러리랑 매우 궁합이 좋은데, 함수 자체를 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;
};

함수 객체

함수 객체에 대한 보충 설명은 여기를 참고하자!


단점

잘못 사용하면 피를 볼 수 있다. 완전 안전하지는 않다.
어떻게 하면 위험할까?

파기된 주소값을 사용할 수도 있게 되는 문제. 사실 람다의 탓이 아닌 것이다!


활용 예시

  1. 정렬에 람다식 활용
std::vector<int> numbers = {4, 2, 7, 1, 9};
std::sort(numbers.begin(), numbers.end(), [](int a, int b){
 return a < b;
});
  1. 조건에 맞는 요소 찾기 (find_if) 에 람다식 활용
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;
}};
  1. 벡터의 모든 요소에 대해 작업하기 (for_each) 에 람다식 활용
std::vector<int> numbers = {5, 2, 7, 1, 9};
std::for_each(numbers.begin(), numbers.end(), [](int num){
std::cout << num << " ";
});
  1. 조건에 맞는 요소 제거하기 (remove_if) 에 람다식 활용
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());
  1. 벡터의 모든 요소에 대해 연산 수행하고 결과 반환하기 (transform) 에 람다식 활용
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;
			  });

capture 활용 예시

  1. 외부 변수 캡쳐
    람다식에서 외부에 정의된 변수를 사용할 수 있다.
int multoplier = 2;
auto multiply = [multiplier](int num) {
	return num * multiplier;
};
  1. 값 캡쳐
    외부 변수를 값으로 캡쳐하여 람다식 내부에서 변경할 수 있다.
    mutable 키워드는 람다식 내부의 상태를 변경하는 것을 허용하는 기능으로, 캡쳐된 변수의 값을 변경할 수 있게 해준다.
int count = 0;
auto increment = [count]() mutable {
	count ++;
};
increment();
  1. 참조 캡쳐
    외부 변수를 참조로 캡쳐하여 람다식 내부에서 변경된 값을 외부에서도 사용할 수 있다.
int count = 0;
auto increment = [&count]() mutable {
	count++
};
increment();
  1. 기본 캡쳐
    값 캡쳐와 참조 캡쳐를 함께 사용하여 람다식의 캡쳐 목록을 정의할 수 있다.
int x = 5;
int y = 10;
auto printSum = [x, &y]() {
	std::cout << x + y << std::endl;
};
  1. 캡쳐 지정자
    람다식의 캡쳐 목록을 더 세부적으로 지정할 수 있다.
int x = 5;
int y = 10;
auto printProduct = [&x, y](){
	std::cout << x * y << std::endl;
};
post-custom-banner

0개의 댓글