람다 표현식 (Lambda)

Crat3 ㅣ 2023. 10. 11. 14:01

1. 필요성

일반적으로 람다 문법이 아닌 함수 객체 또는 함수 포인터(펑터)의 문법을 사용하려면, 괄호 연산자를 오버로딩해야 하기 때문에 코드가 길어진다.

아이템이 저장되어 있는 배열에서 특정 희귀도를 찾는 펑터를 표현하면 아래와 같이 나타낼 수 있다.

struct IsUniqueItem
{
    bool operator() (Item& item)
    {
        return item._rarity == Rarity::Unique;
    }
};

STL의 std::find_if 함수를 사용하여 아이템으로 이루어진 벡터 v를 순회한다.

std::find_if(v.begin(), v.end(), IsUniqueItem());

위의 두 단계를 압축하고자 함수의 세번째 인수인 predicate(조건)을 일회성으로 처리하여 코드를 더 단순하게 나타낼 수 있다.

std::find_if(v.begin(), v.end(), [](Item& item) {return item._rarity == Rarity::Unique;});

 

 

2. 문법

람다 문법은 기본적으로 다음과 같이 나타낸다.

[] () { }

괄호 사이에는 input type, 중괄호 사이에는 구현부가 들어간다.

[](Item& item)
{
    return item._rarity == Rarity::Unique;
}

auto와 함께 사용하여 람다 표현식을 변수 방식으로 들고 있을 수 있다.

auto findByRarity = [](Item& item)
{
    return item._rarity == Rarity::Unique;
}

일반적으로 람다 표현식은 리턴 타입을 컴파일러가 추론하여 반환한다.

하지만 명시적으로 추론을 유도할 수도 있다.

[](Item& item) -> int
{
    return item._rarity == Rarity::Unique;
};

 

-> 아래와 같이 한 줄로 나타내어 사용할 수 있다.

[](Item& item) -> int { return item._rarity == Rarity::Unique; };

 

 

3. 캡쳐 (Capture)

람다에 내장된 캡쳐 기능을 이용하여 외부 변수(혹은 값)을 람다 내부에 가져오는 방법이다.

람다 내부에 변수를 가져올 때 복사를 하여 가져오거나 혹은 참조를 하여 가져올 수 있다.

 

wantedId라는 변수를 통해 각 아이템의 itemId 중 원하는 Id를 찾아주는 predicate을 만들어서 std::find_if로 찾아본다.

std::find_if(v.begin(), v.end(), [](Item& item) {return item._itemId == wantedId;});

 

1) 복사( '=' )

std::find_if(v.begin(), v.end(), [=](Item& item) {return item._itemId == wantedId;});
std::find_if(v.begin(), v.end(), [wantedId](Item& item) {return item._itemId == wantedId;});

외부 변수의 값을 그대로 복사해서 가져온다.

람다 내부에서 이 값을 변경해도 원래 변수의 값은 변하지 않는다(복사의 특성).

 

2) 참조( '&' )

std::find_if(v.begin(), v.end(), [&](Item& item) {return item._itemId == wantedId;});
std::find_if(v.begin(), v.end(), [&wantedId](Item& item) {return item._itemId == wantedId;});

외부 변수의 값을 참조하여 가져온다.

람다 내부에서 이 값을 변경하면 원래 변수의 값도 변한다(참조의 특성).

 

 

4. 주의점

람다 표현식을 사용할 때 캡쳐를 사용하는 방법에 따라 에러를 유발할 수 있다.

 

1) 잘못된 주소를 참조하는 변수를 캡쳐할 때

-> [&]와 같이 일괄적으로 적용하지 않고 명시적으로 넘겨줄 변수에만( [&wantedId] ) 쓰는 방법을 사용한다.

 

2) 주소를 복사할 때

아래에 Knight 클래스의 HP를 출력하는 코드를 작성한다.

class Knight
{
public:
    auto PrintHp()
    {
        auto print = [=]()
        {
            cout << _hp << endl;
        };
        return print;
    }
    
public:
    int _hp = 100;
};

위의 print 내의 _hp는 사실 this->_hp 이다. 즉 this 포인터가 생략되어있다.

위의 표현식이 발동되면 this 포인터의 멤버 변수인 _hp를 넘겨주도록 되어있으므로, 엉뚱한 메모리 주소를 사용하게 될 수 있다.

'복습' 카테고리의 다른 글

스마트 포인터 - shared_ptr  (0) 2023.10.11
오른값 참조 (r-value Reference)  (0) 2023.10.11
함수 객체 ( functor ; 펑터 )  (0) 2023.10.06
함수 포인터  (0) 2023.10.06
우선순위 큐 (priority_queue)  (1) 2023.10.05