<개인공부>/[C++]

[C++] 람다 표현식, lambda에 대해서

BlockDMask 2021. 5. 17. 00:30
반응형

안녕하세요. BlockDMask입니다.
오늘은 C++11, 14에서 추가된 lambda 표현식에 대해 알아보겠습니다.

<목차>
1. 람다 표현식
2. 람다 표현식 사용 방법과 구조
3. 람다의 필요성, 사용 예제

 

1. C++ 람다 표현식


lambda는 람다 표현식, 람다 함수, 익명 함수 등의 이름으로 불립니다.
일단 람다 함수가 어떻게 생겨먹었는지 먼저 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<string>
using namespace std;
 
// 일반 함수 정의
void sum1(int a, int b)
{
    cout << "sum1 func : " << a + b << endl;
}
 
int main(void) {
 
    //일반 함수 호출
    sum1(1020);
 
    //람다 함수
    [](int a, int b)
    {
        cout << "sum2 lambda : " << a + b << endl;
    }(3040);
 
    return 0;
}
 
cs

동일한 동작을 하는 함수를 일반 함수와 람다 함수로 만들어보았습니다.

위 코드를 보면 우리가 일반적으로 알고 있는 함수의 모양인
반환형 함수이름 (매개변수)
{
  // 함수동작
}

이런 식으로 함수가 이루어져 있는 걸 볼 수 있는데요.

아래 람다함수를 보면
[] (int a, int b) {  cout << " " << endl; } (30, 40);

이런 식으로 함수의 모양이 이상한 것을 볼 수 있습니다.

함수의 이름과 반환형이 보이지 않는 것을 볼 수 있습니다.
[] (매개변수) { // 함수 동작 } (호출 시 인자) ; 

이런 식으로 람다 함수가 이루어진 것을 볼 수 있습니다.

이렇게 우리가 아는 일반 함수에서 함수 이름이 없어지고 동작만 있는 함수를 람다 함수, 이름 없는 함수, 람다 표현식이라고 합니다. 

좀 더 자세한 것은 아래에서 확인해보겠습니다.

 

 

2. C++ lambda 사용법과 구조


2-1) 람다 기본 모양과 사용법

람다 함수는 아래와 같이 대괄호[], 소괄호(), 중괄호{}, 소괄호() 이런 모양으로 생겼습니다.
여기서 생략이 가능한 건 소괄호들 뿐입니다.

[] ( ) {  } ( )
[캡처] (매개변수) { 함수 동작 } (호출인자)

첫 번째 [] : 캡처
두 번째 () : 매개변수 선언 부분 (생략 가능 - 매개변수 필요 없을 때)
세 번째 {} : 함수 동작 부분
네 번째 () : 함수 호출 시 인자 (생략 가능 - 호출 시에만 사용)

이런 식으로 함수가 이루어져 있습니다. 아까 예로 들었던 매개변수가 2개 있던 함수를 보면

[] (int a, int b) { cout << a + b << endl; } (10, 20)

이런식으로 람다 표현식이 정의되어있었습니다.

사실 정확하게 이야기하면 표현식은 앞에 파란 부분 [] (int a, int b) { cout << ... << endl; } 이 부분까지가 람다 표현식이고 뒤에 (10, 20)은 람다 함수를 호출할 때 사용하는 것입니다.

살짝 정리를 해보면 아래와 같습니다.

람다 표현식 생성. 즉, 함수를 만들기만 한 거
[](int a, intb) {return a + b};

람다 표현식 사용. 즉 함수를 만들고 호출한 것
[](int a, intb) {return a + b} (10, 20);

 

일반 함수와 마찬가지로 매개변수가 있는 경우, 매개변수가 없는 경우, 반환이 있는 경우, 반환이 없는 경우 모두 구현이 가능합니다.

매개변수 없는 람다 표현식 : [] { cout << " 인자 없음 " << endl; };

매개변수 있는 람다 표현식 : [] (int a, int b, int c) { cout << a << b << c << endl; };

매개변수 없고 반환 있는 람다 : [] { return 200; };

매개변수 있고 반환 있는 람다 : [] (int a, int b) { return a * b; } ;

이런 식으로 4종류의 람다 함수가 존재합니다.

*그리고 람다 표현식도 식이기 때문에 끝에는 세미콜론을 붙여주어야 합니다.

위에서는 함수를 호출하지 않았으므로 중괄호 뒤에 () 이런 식이 없는 것을 확인할 수 있습니다.
앞서 이야기했듯이 매개변수가 있는 람다를 호출하기 위해서는  [] (int a, int b) { return a * b; } (10, 20) ; 이런 식으로 맨 뒤에 매개변수에 맞게 호출부를 작성해주어야 합니다.

 

2-2) 람다 캡처 사용법

이제 람다 모양 [] () {} () 중 맨 앞에 있던 [] 대괄호에 대해서 설명해보겠습니다. 
일단 이 대괄호 부분을 캡쳐라고 부릅니다.

캡처는 람다 외부에 정의되어있는 변수나 상수를 람다 내부에서 사용하기 위해서 사용합니다.

"어 그럼 매개변수로 넘겨서 사용하면 되는 거 아님?" 이럴 수 있지만, 
모든 변수, 상수를 매개변수로 전달할 수도 없고, STL에서 사용할 때는 매개변수 제약이 있을 수 있기 때문에 이런 상황에서 캡처를 사용하라고 캡쳐를 만들어 둔 것으로 판단됩니다.

캡처도 방식이 두 가지가 있는데요. 우리가 배웠던 call by value, call by reference 이것입니다.
즉 변수를 참조로 캡처하느냐 , 복사로 캡쳐하느냐 이 차이입니다.

참조를 할 때는 & 기호를 붙이고, 복사를 할때는 그냥 변수 이름을 사용하면 됩니다.
만약 모든 외부 변수에 대해서 참조를 하려면 [&]라고 사용하면 되고
모든 외부 변수에 대해서 복사를 하려면 [=]으로 사용하면 됩니다.

외부에 변수가 result1, result2, result3, result4 가 있다고 하면 아래와 같은 의미가 됩니다.
[result1, result2] () {} () // 변수 result1, result2를 복사해서 람다 함수 내부에서 사용
[&result1, &result2] () {} () // 변수 result1, result2 를 참조해서 람다 함수 내부에서 사용
[result3, &result4] () {} () // 변수 result3은 복사 result4는 참조해서 람다 함수 내부에서 사용
[=] () {} () // 모든 외부 변수 result1, result2, result3, result4를 복사해서 람다 함수 내부에서 사용

[&] () {} () // 모든 외부 변수 result1, result2, result3, result4 를 참조해서 람다 함수 내부에서 사용
[&, result3] () {} () // 모든 외부 변수 (result1,2,4)은 참조로 사용하지만, result3만 복사로 사용
[=, &result3] () {} () // 모든 외부 변수 (result1,2,4)은 복사로 사용하지만, result3만 참조로 사용

바로 코드로 확인해보겠습니다.

 

예제 1) 람다 캡처 일부 복사, 일부 참조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
#include<string>
using namespace std;
 
int main(void) {
    int result1 = 1;
    int result2 = 2;
    int result3 = 3;
    int result4 = 4;
 
    // 일부 복사
    cout << "1. 특정변수 복사" << endl;
    [result1, result2](int a, int b) { 
        cout << "result1, result2 : " << result1 << ", " << result2 << endl;
        cout << "result1 + a + b : " << result1 + a + b << endl;
 
        // 복사한 변수에는 대입 불가능
        // result1 = a + b; 
        // result2 = 99999;
    }(1020);
 
 
 
    // 일부 참조
    cout << "2. 특정변수 참조" << endl;
    [&result3, &result4](int a, int b) {
        result3 = 22222;
        cout << "(내부) result3 : " << result3 << endl;
        result4 = a + b;
    }(1020);
 
    cout << "(외부) result3, result4 : " << result3 << ", " << result4 << endl;
 
    return 0;
}
 
cs

이런 식으로 외부 변수를 변경하지 않고 사용만 할 것이라면 [변수] 캡처를 이용해서 복사를 해서 사용하면 되고,
외부 변수를 가지고 와서 변경을 하려면 [&변수] 캡처를 이용해서 참조하면 됩니다.

 

 

예제 2) 전체 참조, 전체 복사

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
#include<string>
using namespace std;
 
int main(void) {
    int result1 = 1;
    int result2 = 2;
    int result3 = 3;
    int result4 = 4;
 
    // 전체 복사
    cout << "3. 전체 복사" << endl;
    [=](int x)
    {
        cout << "result1, 2 : " << result1 << ", " << result2 << endl;
        cout << "result3, 4 : " << result3 << ", " << result4 << endl;
        cout << "매개변수 : " << x << endl;
    }(30);
    
 
     cout << endl;
 
 
    // 전체 참조
     [&](int y)
     {
         cout << "result1, 2 : " << result1 << ", " << result2 << endl;
         cout << "result3, 4 : " << result3 << ", " << result4 << endl;
         cout << "매개변수 : " << y << endl;
 
         result1 += y;
         result2 += y;
         result3 += y;
         result4 += y;
     }(99);
 
     cout << "result1, 2 : " << result1 << ", " << result2 << endl;
     cout << "result3, 4 : " << result3 << ", " << result4 << endl;
 
    return 0;
}
 
cs

[=] 이런 캡처를 통해서 외부 변수를 전부 복사해서 람다 함수 내부에서 사용할 수 있습니다.
복사한 변수에 대입을 하려 하는 건 불가능합니다. (rvalue)

[&] 이런 캡처를 통해서 외부 변수를 전부 참조하여 람다 함수 내부에서 사용할 수 있습니다.
참조 변수에 대입을 하거나, 하는 변경을 할 수 있으며, 당연하게도 그것은 람다 함수 외부에서도 적용이 됩니다.

 

 

예제 3) 전체 참조 + 일부 복사 / 전체 복사 + 일부 참조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<iostream>
#include<string>
using namespace std;
 
int main(void) {
    int result1 = 1;
    int result2 = 2;
    int result3 = 3;
    int result4 = 4;
 
    // 전체 참조, 일부 복사
    cout << "5. 전체 참조, 일부 복사" << endl;
 
    [=&result3, &result4](int x)
    {
        cout << "result1, 2 : " << result1 << ", " << result2 << endl;
        cout << "result3, 4 : " << result3 << ", " << result4 << endl;
        cout << "매개변수 : " << x << endl;
 
        //result1 += x; // 전체 복사
        //result2 += x; // 전체 복사
        result3 += x; // 일부 참조
        result4 += x; // 일부 참조
    }(30);
 
    cout << "result1, 2 : " << result1 << ", " << result2 << endl;
    cout << "result3, 4 : " << result3 << ", " << result4 << endl;
 
     cout << endl;
 
 
     // 전체 복사, 일부 참조
     cout << "6. 전체 복사, 일부 참조" << endl;
 
     [&, result1, result2](int y)
     {
         cout << "result1, 2 : " << result1 << ", " << result2 << endl;
         cout << "result3, 4 : " << result3 << ", " << result4 << endl;
         cout << "매개변수 : " << y << endl;
 
         //result1 += y; // 일부 복사
         //result2 += y; // 일부 복사
         result3 += y; // 전체 참조
         result4 += y; // 전체 참조
     }(99);
 
     cout << "result1, 2 : " << result1 << ", " << result2 << endl;
     cout << "result3, 4 : " << result3 << ", " << result4 << endl;
 
    return 0;
}
 
cs

위 예제에서 보신 것처럼 [=, &변수, &변수] 이런 식으로 전체 외부 변수를 복사 하되, "일부" 변수만 참조로 가지고 올 수 있습니다.

또한 반대로 [&, 변수, 변수] 모든 변수를 참조로 가지고 오고, "일부" 변수만 복사로 가지고 올 수 있습니다.

단, [&, &변수] 이런식으로 모든 변수 참조하고 "일부"도 참조하려 하거나, 
[=, 변수, 변수] 이런 식으로 모든 변수 복사하고 "일부"도 복사하려는 것은 당연하게도 오류가 발생합니다.

 

 

 

3. C++ 람다의 필요성과 사용 예제


3-1) 람다와 auto

람다 함수의 정의를 우리는 auto를 이용해서 특정 변수에 넣어둘 수 있습니다.

auto func1 = [] (int a, int b) { return a * b };

이런 식으로 함수를 정의한 부분을 func1 이라는 변수에 집어 넣어서 실제로 사용할때는 
fun1(10, 20); 이런식으로 사용이 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
using namespace std;
 
int main(void) {
    // 람다 함수1
    auto func1 = [](int a, int b) {return a * b; };
 
    cout << "func1(2, 10) : " << func1(210<< endl << endl;
 
 
    // 람다 함수2
    int num = 20;
    auto func2 = [&num](int a) { num += a; };
 
    func2(100); // num = num + 100 
 
    cout << "num : " << num << endl;
    return 0;
}
 
cs

auto를 통해서 람다 함수를 변수에 받아 넣을 수 있습니다.

 

 

3-2) 함수의 파라미터가 되는 람다

자 이제 람다를 조금 배워봤는데, '그래서 람다 어디에 쓰지?' 하시는 분들이 계실 것입니다.
저는 람다 함수는 아래와 같은 상황에 많이 사용합니다.

우리가 std::sort 함수에서 정렬을 위해서 3번째 매개변수로 함수를 넘겨주는 경우가 있습니다.
이렇게 함수는 필요한데 이번 한번 사용하고 땡처리되는 1회성 함수가 필요할 때 사용합니다.

즉, "함수가 필요한데 많이 쓰일 것 같진 않고, 애매~할 때" 사용합니다.
아래와 같이 sort 함수에서 세 번째 인자로 함수를 넣을 때 딱 이번에만 사용할 건데 함수를 만들어야 하는 그런 상황에 람다 함수를 사용하면 아주 유용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include<algorithm>
#include<array>
using namespace std;
 
bool compare(int a, int b) { return a > b; }
 
int main(void) {
    std::array<int10> arr1 = { 5421100322469 };
    std::array<int10> arr2 = { 5421100322469 };
    std::array<int10> arr3 = { 5421100322469 };
 
    // sort 함수
    sort(arr1.begin(), arr1.end());
    cout << "std::sort(arr1, arr1 + 10)" << endl;
    for (int val : arr1)
    {
        cout << val << " ";
    }
    cout << endl << endl;
 
 
    // sort 함수와 일반 함수 이용
    cout << "std::sort(arr, arr + 10, compare) : " << endl;
    sort(arr2.begin(), arr2.end(), compare);
    for (int val : arr2)
    {
        cout << val << " ";
    }
    cout << endl << endl;
 
 
    // sort 함수와 람다 함수 이용
    cout << "std::sort(arr, arr + 10, [](int a, int b) {return a > b; })" << endl;
    sort(arr3.begin(), arr3.end(), [](int a, int b) {return a > b; });
    for (int val : arr3)
    {
        cout << val << " ";
    }
 
    return 0;
}
 
cs

첫 번째는 sort함수의 기본 사용이며, 이 경우 디폴트로 오름차순으로 정렬이 됩니다.

두 번째 예제는 sort() 함수에서 세 번째 인자에 정렬 기준을 정하는 함수를 넣음으로써 내림차순으로 정렬하도록 하는 예제입니다.
sort(arr2.begin(), arr2.end(), compare); 여길 보시면 세 번째 인자로 함수를 넣은 것을 볼 수 있습니다. 
기존에는 이런 식으로  sort 함수를 이용해왔는데 람다 함수를 이용하면 좀 더 간편하게 처리할 수 있습니다.

세 번째 예제를 보면 sort(arr3.begin(), arr3.end(), [](int a, int b){return a > b;}); 이런 식으로 sort 함수의 세 번째 인자에 일반 함수 대신, 람다 함수를 집어넣은 것을 볼 수 있습니다.
이런 식으로 함수가 필요한 곳에 함수 대신 람다 함수를 사용할 수 있습니다.

 

* sort 함수
: 배열의 시작 주소와 마지막 다음 주소를 받아서 요소를 퀵 소트 알고리즘으로 정렬하는 C++ 표준 함수
: 3번째 인자로 함수를 전달하면 요소의 비교 정책을 변경할 수 있습니다.
더 다양한 sort 함수 예제가 궁금하시다면 [바로가기]

 

이상으로 C++11, 14의 람다 표현식, 람다 함수에 대해서 알아보았습니다. 감사합니다

반응형