람다가 있기전에도 함수자(function)를 사용해서 만들 수 있긴 하다.
하지만 람다식을 배우게 된다면 더욱 더 편리하게 작업 할 수 있다!
그래서 중요하다!!
우리가 함수자나 함수 포인터를 실전에서 언제 사용하냐를 보면 predicate를 넣어줄 때나 인벤토리를 만들 때 사용한다.
인벤토리를 만들게 된다면 아래처럼 구현하게 될 것이다.
enum class ItemType
{
None,
Armor,
Weapon,
Jewelry,
Consumable
};
enum class Rarity
{
Common,
Rare,
Unique
};
class Item
{
public:
Item() {}
Item(int itemId, Rarity rarity, ItemType type) : _itemId(itemId), _rarity(rarity), _type(type) {}
public:
int _itemid = 0;
Rarity _rarity = Rarity::Common;
ItemType _type = ItemType::None;
};
int main()
{
vector<Item> v;
v.push_back(Item(1, Rarity::Common, ItemType::Weapon));
v.push_back(Item(2, Rarity::Common, ItemType::Armor));
v.push_back(Item(3, Rarity::Rare, ItemType::Jewelry));
v.push_back(Item(4, Rarity::Unique, ItemType::Weapon));
}
함수자, 함수포인터가 도움이 되었던 부분은 우리가 유니크인 아이템을 찾고싶다! 라고 하면 일일이 순회하는 코드를 만들면 코드가 방대해질 것이다.
만약 find_if를 사용한다면
struct IsUniqueItem
{
bool operator()(Item& item)
{
return item._rarity == Rarity::Unique;
}
};
std::find_if(v.begin(), v.end(), IsUniqueItem());
이런식으로 사용할 수 있었다.
하지만 매번 struct를 만드는 것은 너무 지저분하다.
그래서 오늘 배우는 람다식을 이용할 것이다.
람다는 익명함수뿐만 아니라 함수 객체에 더 가깝다고 볼 수 있다. (상태까지 저장이 가능하다고 한다)
문법
일단 람다를 사용할 때
[](){}를 적고 시작한다.
우리가 함수를 만들 때 처럼 ()부분은 Input을 넣어주고 {}부분은 구현을 해주면 된다.
만약 우리가 위에 IsUniqueItem을 만들고 싶다면
(){}이 부분은 정해져 있을 것이다. 그리고 람다라는 이유로 []를 붙여주면 된다.
[](Item& item)
{
return item._rarity == Rarity::Unique;
}
그래서 사용할 때는
std::find_if(v.begin(), v.end(), [](Item& item) { return item._rarity == Rarity::Unique; });
이런식으로 사용해주면 된다.
객체를 만들어서 사용하고 싶다면
auto IsUniqueLambda = [](Item& item) { return item._rarity == Rarity::Unique; };
std::find_if(v.begin(), v.end(), IsUniqueLambda);
이런식으로도 사용할 수 있다.
단발성을 함수를 사용할 때 아주 유용하다.
그리고 우리가 여기서 위처럼만 사용을 한다면 익명 함수라고 부른다. (일회성 함수)
또 Input은 ()에 받는데 return type은 어떻게 판단하는 걸까? 이 생각을 할 수 있는데 auto와 template와 비슷하게 추론을 하는 것이다.
만약에 return type을 지정하고 싶다면
[](Item& item) -> int { return item._rarity == Rarity::Unique; };
이런식으로 사용해주면 된다. (넣지 않으면 자동 추론)
여기까지가 익명함수에 관한 내용이다.
우리가 함수 포인터를 사용할 때 상태를 가질 수 없다는 단점이 존재하였다.
만약 특정 아이템을 찾고 싶다! 라고 하면
struct IsWantedItem
{
bool operator()(Item& item)
{
return item._itemId == wantedId;
}
int wantedId = 0;
}
IsWandtedItem isWantedItem;
isWantedItem.wantedId = 2;
이렇게 상태를 가지고 객체처럼 만들어서 사용할 수 있었던 것이 함수 객체의 장점이 였는데
함수 객체의 기능까지 람다가 가지고 있다.
int wantedId = 2;
[&wantedId](Item& item)
{
return item._itemId == wantedId;
};
[]캡쳐 기능을 사용여 저장할 수 있다.
캡쳐 기능을 사용하여 wantedId라는 것을 받을 수 있다.
기본 캡쳐 모드
= 복사
& 참조
[\=] 모든 데이터 복사 방식
[\&] 모든 데이터 참조 방식
복사방식으로 하면 처음값이 계속 똑같이 유지될 것이고
참조방시으로 하면 주소값을 받아서 값을 추출할 것이다.
단일 변수마다 캡쳐 모드도 할 수 있다.
stl 알고리즘 시리즈와 궁합이 잘맞는다.
람다가 다 좋긴하지만 잘못사용하게 된다면 피를 볼 수 있다.
int wantedId = 2;
[&wantedId](Item& item)
{
return item._itemId == wantedId;
};
이런식으로 참조해서 가지고 있을 때 주소값이 날라가 버린다면 날라간 주소를 참조해 문제가 될 수도 있다.
중요한 것!!
람다 문법에 익숙해지기!
[](){}이거 3가지를 적고
일반 함수를 만들듯이
\캡쳐모드 -> return type지정(안해도 됨)
{
내용물(구현부)
}
functor처럼 상태를 가지고 싶을때만 캡쳐모드를 사용하면 된다.
그래서 우리가 앞으로 std::find같은 시리즈를 이용할 때 predicate를 구현할 때 도움이 많이 될 것이다. (어차피 predicate는 거의 한 번만 만들어 준다)
std::find_if(v.begin(), v.end(), [](Item& item){ return item._itemId == 2; }
람다는 처음에는 어렵지만 하다보면 편해져서 많이 사용하게 될 것이다!