관리 메뉴

기억을 위한 기록들

[CPP] 람다 표현식(Lambda Expression)이란? (Lambda 함수) 본문

C & CPP

[CPP] 람다 표현식(Lambda Expression)이란? (Lambda 함수)

에드윈H 2022. 12. 5. 21:26

초안: 2021-03-31

1차수정 : 2022-12-05

 

 

 

 

람다란???

람다 표현식이란 함수나 함수 객체를 별도로 정의하지 않고, 필요한 지점에서 곧바로 함수를 직접 만들어 쓸 수 있는 일종의 익명 함수(이름 없는 함수라고도 할수 있을 것 같다? )혹은 클로저(Closure)를 말한다. 람다 표현식을 잘 활용하면 코드를 깔끔하게 만들 수 있다.

 

사실 람다 함수라는 단어 자체도 원래는 존재하지 않아야 한다고 한다. 우리가 평소에 사용하는 [](){}로 묶인 람다 함수라고 불리우는 것들은 정식적인 명칭으로는 람다 표현식(Lambda Expression)이라고 불러야 한다. 그로 인해서 탄생한 함수를 만드는 문법이기 때문에, 클로저를 만드는 문법 또는 기술이라고 봐야한다. 하지만, 람다 함수라고 하면 일맥상통하니 별로 상관은 없다.

 

 

 

예제

예 : 벡터 정렬하기

#include <vector>
#include <algorithm>

using namespace std;

bool Compare(int a, int b)
{
     return (a > b);
}

int main()
{
    vector<int> arr;


    arr.push back(3);
    arr.push back(2);
    arr.push back(7);
    
    sort(arr.begin(),arr.end(),Compare);
 
 
    return 0;
}

벡터를 정렬하려고 할때 sort 함수를 사용하게 되면 위와 같이 가능하다.

 

그런데 람다식이란

#include <vector>
#include <algorithm>

using namespace std;


int main()
{
    vector<int> arr;


    arr.push back(3);
    arr.push back(2);
    arr.push back(7);
    
    sort(arr.begin(),arr.end(), [](int a, int b) { return (a>b); }); //람다
 
 
    return 0;
}

 

 

 

람다식의 사용 방법(문법)으로

람다 선언자라고 부르는 대괄호 [] 로 시작하고, 그뒤에 람다 표현식의 본문을 담는 중괄호 {}가 나온다.

 

[<captures>] (<parameters>) <specifiers> -> <return_type>
{
  <body>
}

와 같은 식인데, 앞에서부터 보면

 

1. captures(캡쳐)?

제일 앞의 대괄호 []를 캡쳐블록이라고 부른다.

대괄호안에 어떤 변수를 지정하면 람다 표현식의 본문에서 그 변수를 사용하게 만들수 있다. 이를 캡처한다고 표현한다.아무 변수 넣지 않고 비워두면 속한 스코프에 있는 변수를 캡처하지 않는다.

 

캡쳐의 종류

- [] : 비어있음. 캡쳐하지 않음
- = : 값에 의한 캡쳐, 모든 외부 변수를 캡쳐함./ 람다 식 안에서 수정할 수 없음
- & : 참조에 의한 캡쳐, 모든 외부 변수 캡쳐함
- <변수이름> : 특정 변수 값으로 캡처 / 람다 식 안에서 수정할 수 없음
- &<변수이름> : 특정 변수를 참조로 캡처

 

- []를 사용. 캡처하지 않는 경우

//방법1 
auto noCapture = []() {std::cout<< "No Capture"<<std::endl;};
noCapture();

//방법2 바로 실행
[]() {std::cout<< "No Capture2"<<std::endl;};

- =를 사용.  값에 의한 캡쳐 (값 수정은 불가)

int a = 10;
int b = 50;

auto max = [=]() { return a > b ? a : b; };

cout <<"Max is " << max() <<endl;

result :

Max is 50

 

 

- &를 사용. 참조에 의한 캡쳐  (값 수정이 된다.)

	int a = 10;
	int b = 50;

	auto changeValue = [&]()
	{
		a = 20;
		b = 12;
	};

	cout<<a<<" "<<b<<endl;

	changeValue();

	cout <<"result : " << a << " " << b << endl;

resutl :

10 50
result : 20 12

 

- <변수이름>을 넣어 특정 변수값으로 캡쳐 ( 값 수정 불가)

	int a = 10;
	int b = 50;
	int c = 20;

	auto printValue = [a, b]()
	{
		cout << a << "/" << b << endl;
		//여기서 변수 c에 접근할 수 없음
	};

	 printValue();

result :

10/50

 

-<&변수이름>을 넣어 특정 변수을 참조로 캡쳐

	int a = 10;
	int b = 50;
	int c = 20;

	auto changeValue = [&a]()
	{
		a = 100;
		//b와 c는 접근할 수 없음
	};

	 changeValue();

	 cout<< a<< endl;
100/50/20

이 외에도 캡쳐 옵션을 섞을 수도 있다.(캡처 리스트라고도 함)

 

	int a = 10;
	int b = 50;
	int c = 20;

	auto changeValue = [=, &a]()
	{
		a = 100;  //참조에 의한 캡쳐
		cout << b << endl; //값에 의한 캡쳐
	};

	changeValue();

	cout << a <<"/" << b <<"/" << c << endl;
50
100/50/20

 

2. parameters(파라미터) 

선택사항이다.

	auto noCapture = [] {std::cout << "No Capture" << std::endl; }; //빈 괄호를 생략 가능
	noCapture();

	auto noCapture2 = []() {std::cout << "No Capture2!" << std::endl; };
	noCapture2();

예) 두 값 더하기 

int scores1 = 6;
int scores2 = 23;

auto add = [] (int a, int b)
{
	return a + b;
};

cout<< add(scores1, scores2) <<endl;

3. specifiers (지정자)

컴파일러는 람다 표현식을 이름 없는 펑터(함수 객체)로 변환한다. 캡처한 변수는 이 펑터의 데이터 멤버가 된다. 값으로 캡처한 변수는 펑터의 데이터 멤버로 복제 된다.
펑터마다 함수 호출 연산자인 operator()가 구현돼 있다. 람다 표현식의 경우 이 연산자는 기본적으로 const로 설정. 따라서 non-const 변수를 람다 표현식에 값으로 캡처해도 람다 표현식 안에서 이 값의 복제본을 수정 할 수 없다.
다음과 같이 람다 표현식을 mutable로 지정하면 함수호출 연산자를 non-const로 만들 수 있다.
mutable을 지정할때 매개변수가 없더라도 소괄호를 반드시 적어야 한다.

- 선택사항.

- mutable

 1. 값에 의해 캡쳐된 개체를 수정할 수 있게함.

 2. 괜찮은 언어 디자인, 허나 C++에 너무 늦게 들어옴.

int a = 10;


auto changeValue() = [a]() 
{
   cout<<++a<<endl //컴파일 에러
};

cout << changeValue() <<endl;
int a = 10;


auto changeValue() = [a]() mutable //추가
{
   cout<< ++a <<endl //ok
};

cout << changeValue() <<endl;

 

4. return_type(반환형)

- 선택사항

- 반환 형을 적지 않으면 컴파일러가 추론한다. 방법은 일반 함수의 리턴 타입을 추론할때와 같다.

 

- std::function을 이용하여 함수가 람다 표현식을 리턴하게 만들 수 있다.

function<int(void)> multiplyBy2Lambda(int x)
{
	return [x]{ return 2 * x};
}

물론 auto로도 쓸수 있다.

auto fn =  multiplyBy2Lambda(5);
cout<< fn() <<endl; //결과는 10

 

auto multiplyBy2Lambda(int x)
{
	return [x]{ return 2 * x};
}

 

 

람다 식의 장점

- 간단한 함수를 빠르게 작성할 수 있음.

auto max = [](int a,int b) { return a > b ? a : b; };

 

 

람다 식의 단점

- 디버깅하기 힘들어짐. (콜스택 보기가 힘듦)

- 함수 재사용성이 낮음

- 사람들은 보통 함수를 새로 만들기 전에 클래스에 있는 기존 함수를 찾아봄

- 람다 함수는 눈에 잘띄지 않아서 못찾을 가능성이 높음

- 코드 중복 발생 할 여지가 있음.

 

최적의 사용은?

1. 기본적으로는 이름 있는 기본 함수를 쓰자.

2. 자잘한 함수는 람다로 써도 괜찮다 ( 한줄짜리 함수)

3. 허나 재사용할 수 있는 함수를 찾기 어렵다.

4. 정렬함수처럼 STL컨테이너에 매개변수로 전달할 함수들도 좋은 후보이다.

 

 

 

실질적으로 간단한 내용의 함수를 작성할때 쓰면 좋긴 하다.(ex. 1줄짜리의 내용 함수정두? 내 생각엔 5줄이하 까지는 무난하게 괜찮지 않을까 싶다.) 하지만, 선택은 본인 몫이고, 팀에서 사용하는 룰이 있다면 그 룰에 따르는게 좋은것같다.

 

 

 

참고 : 
https://namu.wiki/w/%EB%9E%8C%EB%8B%A4%EC%8B%9D

 

람다식 - 나무위키

이 저작물은 CC BY-NC-SA 2.0 KR에 따라 이용할 수 있습니다. (단, 라이선스가 명시된 일부 문서 및 삽화 제외) 기여하신 문서의 저작권은 각 기여자에게 있으며, 각 기여자는 기여하신 부분의 저작권

namu.wiki