bdfgdfg

[C++ 11] 람다(lambda) 식 본문

게임프로그래밍/C++

[C++ 11] 람다(lambda) 식

marmelo12 2022. 1. 24. 18:44
반응형

람다(lambda) 식

간단하게 말하면 이름없는 함수를 의미한다.

함수를 호출하는 방법은 C++에서는 다양한 방법이 존재한다.

 

1. 일반 전역 함수, 멤버 함수등

2. 함수 객체 ( 클래스 () 연산자 오버로딩 )

3. 함수 포인터사용

 

여기서 벡터 컨테이너의 요소를 정렬하는 코드를 보면.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool Compare(std::pair<Test, int> lhs, std::pair<Test, int> rhs)
{
    return lhs.first.GetValue() > rhs.first.GetValue(); // 내림차순 정렬
}
 
int main()
{
    srand(time(nullptr));
    std::vector<std::pair<Test, int>> vc;
    vc.reserve(100);
    for (int i = 0; i < 100++i)
    {
        int random = (rand() % 300+ 1;
        Test t(random);
        vc.push_back(std::make_pair(t, i));
    }
 
    std::sort(vc.begin(), vc.end(),Compare);
 
    return 0;
}
cs

위와 같이 내림차순 정렬이 가능하다. (혹은 함수객체를 사용하는 방법도 존재한다)

그런데 저 벡터의 요소를 저기서 단 한번만 정렬을 하고, 단순히 객체의 멤버 하나로 정렬을 하는건데 굳이 함수 객체나 함수를 만들어서 정렬을 하는건 코드 라인 수만 잡아먹는다는 생각이 든다.

 

이럴 때 사용할 수 있는게 람다 식

1
2
3
4
5
6
7
std::sort(
        vc.begin(), vc.end(),
        [](std::pair<Test, int> lhs, std::pair<Test, int> rhs) -> bool //반환형 선언 ->는 선택사항.(자동추론)
        {
            return lhs.first.GetValue() > rhs.first.GetValue(); // 내림차순 정렬
        }
    );
cs

정렬을 위한 함수나 함수 객체를 따로 만들어 줄 필요없이 이름 없는 함수 람다 식을 사용하면 더욱 간결하게 처리할 수 있다.

더욱 장점인 점은 함수의 인라인(inline,함수호출 과정이 존재하지 않음)화 되기에 함수 호출의 미세한 오버헤드도 줄일 수 있다.

 -> 함수 포인터를 사용하는 경우 인라인화 되지 않는다. (inline함수는 매크로 함수처럼 컴파일 타임에 치환)

 

즉 람다 식은 이름 없는 함수이며 인라인으로 정의와 호출을 한번에 하는 함수.

 -> 이름 없는 함수 객체

  --> 메모리 상에 임시적으로 객체가 생성된다. (함수 객체처럼 행동)

 -> 내포되는 함수

 

이제 람다 식의 문법을 자세히 살펴보면

출처 - POCU

 

매개변수 목록과 반환 형은 보기만 해도 알 수 있다.

캡처블록과 지정자만 보자.

 

캡처블록에 람다 함수안이 아닌 외부 변수를 넣는다면 이를 캡쳐하여 해당 외부 변수를 람다 내부에서 이용할 수 있게 된다.

1
2
3
4
5
6
7
8
9
int main()
{
    int temp = 50;
    auto func = [&temp](int value) { temp += value; std::cout << temp; };
 
    func(100);
    
    return 0;
}
cs

실행 결과는

람다 함수도 함수이기에 외부 변수에 대한 정보가 없다면 접근이 불가능한게 당연하다.

 

먼저 캡처목록의 종류를 살펴본다.

1. [] - 빈 캡처목록. 

2. [=] - 외부의 모든 변수들을 값으로 가져온다. (Call-by-value)

3. [&] - 외부의 모든 변수들을 레퍼런스로 가져온다. (Call-by-reference)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
    int temp = 50;
    int temp2 = 100;
 
    //Call-by-Value
    auto Max = [=]() -> int { return (temp > temp2) ? temp : temp2; };
    //Call-by-reference
    auto Min = [&]() -> int {return (temp > temp2) ? temp2 : temp; };
 
    std::cout << "Max 값은 " << Max() << std::endl;
    std::cout << "Min 값은 " << Min() << std::endl;
    
    return 0;
}
cs

참고로 값에의한 캡쳐는 값의 수정이 불가능하다.

=,&는 외부의 모든 변수에 접근할 수 있다.

그러길 원치 않고 명시적으로 외부 변수들 중 원하는 변수만으로도 캡쳐가 가능하다.

(값 수정 불가능!)

혹은 외부 변수를 모두 값에 의한 캡쳐를 하면서 특정 변수만 참조에 의한 캡쳐도 가능하다.

1
2
//가능!
auto test = [=&temp]() { temp = 100std::cout << temp2 << temp3; };
cs

 

마지막으로 지정자.

해당 하는 곳에 mutable이라는 키워드를 넣을 수 있다.

의미하는바는 간단한데 값에 의한 캡쳐 외부 변수들을 수정할 수 있게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main()
{
    int temp = 50;
    int temp2 = 100;
    int temp3 = 25;
    std::cout << "람다 호출 전 값 \n" << temp << "\n" << temp2 << "\n" << temp3 << std::endl;
    //값에 의한 캡쳐(Call-by-Value)
    auto test = [=]() mutable 
    { 
        temp = 100;  // 수정 가능!
        temp2 = 200// 수정 가능!
        temp3 = 300// 수정 가능!
        std::cout << "람다 호출 중 값 \n" << temp << "\n" << temp2 << "\n" << temp3 << std::endl;
    };
    test();
    std::cout << "람다 호출 후 값 \n" << temp << "\n" << temp2 << "\n" << temp3 << std::endl;
    return 0;
}
 
cs

당연하지만 값에 의한 호출이기에 람다 내부에서 값을 수정해도 실제 값은 변경되지 않는다.

 

 

 

람다 식은 사용하기 편리하다는 장점이 존재하지만, 디버깅이 힘들고 함수의 재사용성이 낮다는 단점도 존재한다.

 -> 기본적으로는 이름 있는 함수 사용.

 -> 한 줄짜리 함수 혹은 단 한번 쓰이고 말 함수(위의 예제와 같은 케이스 -> 정렬 함수)

반응형
Comments