[ Modern C++ : Lambda ]
람다 = 함수 객체를 손쉽게 만드는 문법
람다 자체로 C++11에 '새로운' 기능이 들어간 것은 아니다. 템플릿으로 구현할 순 있었다.
람다의 필요성을 느끼기위해 예를 들어보자.
std::find_if()에 사용할 Predicate을 위해 struct에 bool operator()를 만들어서 함수 객체(functor)를 만들어서 사용했다고 가정해보자.
이 함수 객체는 여기서 단 한번만 쓰이고 더 이상 쓰이지 않을 경우, 매번 일회용 함수 객체를 일일이 만들어주고 코드에 계속 남아있는 번거로움이 생긴다.
기본 형태 : auto "functor name" = [] () -> (return type) { /* Do anything */ };
-> (return type)의 경우 보통 bool로 return하는데, bool이 아니라 int 등 지정하고 싶을 경우에만 추가해서 사용하면 된다.
람다를 사용하면 일회용으로 사용할 함수 객체를 쉽게 만들 수 있다.
→ STL에서 Predicate을 인자로 요구하는 함수들과 시너지가 잘남.
이렇게 람다로 만들어진 일회성 함수 객체를 Closure라 한다.
[ 클로저 (Closure) = 람다에 의해 만들어진 실행시점 객체 ]
다시, Functor로 와서
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
struct FindItemByItemId
{
FindItemByItemId(int itemId) : _itemId(itemId)
{
}
bool operator()(Item& item)
{
return item._itemId == _itemId;
}
int _itemId;
};
|
cs |
위와 같이 생성자를 통해 _itemId까지 부여한다면 찾을 _itemId까지 프로그래머가 원하는대로 지정해서 유연하게 사용할 수 있을 것이다.
그런데 람다로 이렇게 사용하려면 람다 내부에서 비교하려는 값인 itemId의 개념을 모르고 있을 것이다.
이를 위해 사용되는 기능이 [](대괄호)에 있는 캡처(capture) 기능이다.
[ ] 캡처(capture) : 함수 객체 내부에 변수를 저장하는 개념과 유사
[ 캡처 모드 ]
- 값(복사) 방식 [=]
- 참조 방식 [&]
참조 방식으로 캡처할 경우, 위 함수 객체(functor)에서 생성자 인자와 멤버변수가 전부 참조(&)로 바뀐 것과 동일하다.
→ 참조 방식 : 주소 값을 활용해서 넘겨줌, 뒤늦게 itemId 값을 수정한다면 그 수정한 값으로 비교를 함
이제 탐색할 Item item만 받아서 itemId의 조건만 부합하는걸 찾는게 아니라 item의 rarity, type까지 조건을 추가한다고 가정해보자.
조건을 여러개 건다면 함수 객체에서는 똑같이 조건과 변수를 추가하면 되는데,
람다에서는 외부에서 변수를 세팅해야하는 것은 같지만,
람다 내부에서는 바로 그 변수를 가져다가 조건을 쓰면 된다. (함수 객체랑 조건식은 같음)
변수마다 캡처 모드를 지정해서 사용도 가능
: 값 방식(name), 참조 방식(&name)
[&itemId, rarity, type]
[=, &type]
이런식으로 혼용해서도 가능
[ 주의해야할 점 ]
C++에서는 기본적으로 기본 캡처모드 (모두 값 방식 또는 참조 방식으로 캡처하는 것)를 최대한 지양하라고 권고함.
1) 내부적으로 꼼꼼히 살펴보지않으면, 어떤 변수들이 캡처되어서 안에 들어간건지 알기 힘들기 때문 (가독성)
→그래서 캡처하려는 변수들을 [itemId, rarity, type] 이렇게 일일이 넣어주면 가독성이 향상된다.
2) 만약 참조방식으로 쓴 변수에 접긴하기 직전, 주소가 날라갔다면 동작을 예측할 수 없기 때문
또한 복사방식에서도 예측할 수 없는 동작이 일어날 수 있다.
class내에서 람다에 복사방식 캡처를 사용한다면 this 포인터를 넘겨주게 된다.
class Knight 내부의 람다에서는 this 포인터를 넣어준거고, 만약 Knight의 동적 인스턴스인 k를 delete로 날렸다고 가정하자.
외부에서 ResetHpJob()을 실행 후 this 포인터를 return받는 job(이건 함수 객체라서 실행 가능)을 실행시킨다면, 이미 해제된 this 주소의 _hp에 해당하는 offset에 200으로 수정하기 때문에 메모리 오염이 일어나게 된다.
→ 그렇기 때문에 람다를 사용할 때는 기본 캡처 모드, 즉 모든 변수들을 값(복사) 방식, 참조 방식으로 쓰는걸 지양하라고 한다
만약 캡처할 때 명시적으로 this를 사용한다면 코드에서 명시적으로 this 포인터를 넘겨주는 것이 보이기 때문에 조심할 확률이 높다. 또는 변수마다 캡처 모델을 지정해서 사용하는 것이 더 추천되는 편이다.
이제 functor를 매번 하나씩 직접 만드는 것 보다, lambda 문법을 이용해서 한번에 넣어주는 것이 좀 더 깔끔할 것이고
특히 STL에서 functor나 Predicate를 인자로 요구하는 함수를 사용할 때 Lambda를 이용한다면 코드 가독성이 향상되고 프로그래밍 생산성도 좋아지게 될 것이다.
[ Reference ]
Rookiss, C++ Part.1 입문 - 람다(Lambda)
'컴퓨터공학 > C++' 카테고리의 다른 글
[Modern C++] Smart Pointer (1) | 2024.09.27 |
---|---|
[Modern C++] std::forward (0) | 2024.09.14 |
[C++] 동적할당 (0) | 2024.08.23 |
[C++] 초기화 리스트 (0) | 2024.08.22 |
[C++] 상속성 (0) | 2024.08.21 |