메모내용
Nav

Lambda Expression (람다 표현식, 람다함수, 익명함수)

  • 람다 표현식
  • 람다 표현식 메모리
  • 람다 표현식 의미

    람다 표현식이란 함수나 함수 객체를 별도로 정의하지 않고, 필요한 지점에서 곧바로 함수를 직접 만들어 쓸 수 있는 일종의 익명 함수(이름 없는 함수) 혹은 클로저(Closure)를 말한다. 람다 표현식을 잘 활용하면 코드를 깔끔하게 만들 수 있다.

    보통 함수포인터를 인수로 갖는 함수에서, 따로 선언 없이 바로 람다식을 이용해서 넣어주는 경우들이 많다. 정렬 하려는데 조건식이 필요하다든지, 제거 하려는제 조건식이 필요하다든지, 델리게이트에서 특정상황에 어떤 기능을 하도록 넣어준다든지

    JavaScript의 ArrowFunction 과 유사하다

                            
                                #include <iostream>
            
                                    int main()
                                    {
                                        // ->반환형 형태로 쓰거나 
                                        int a = []()->int{
                                            return 3+5;
                                        }();
                                        
                                        // 반환형을 아예 안쓰거나 
                                        int a = [](){
                                            return 3+5;
                                        }();
                                        
                                        printf("%d", a);
                                        
                                        return 0;
                                    }                        
                            
                        

    예제 1

    벡터 정렬하기

    일반적인 방법

                            
                                
                    #include <vector>
                    #include <algorithm>
                
                    using namespace std;
                
                    // 정렬에 사용할 함수
                    bool Compare(int a, int b)
                    {
                        return (a > b);
                    }
                
                    int main()
                    {
                        vector<int> arr;
                
                
                        arr.push back(3);
                        arr.push back(2);
                        arr.push back(7);
                        
                        // 이렇게 정렬에 사용할 함수를 미리 선언하고 적용할 수도 있지만
                        sort(arr.begin(),arr.end(),Compare);
            
                        // 람다식을 이용해서 따로 함수선언 필요없이 바로 조건식을 적어줄 수도 있다.
                        sort(arr.begin(),arr.end(), [](int a, int b) { return (a>b); }); //람다
                    
                    
                        return 0;
                    }
                            
                        

    사용법

    람다 선언자라고 부르는 대괄호 [] 로 시작하고, 그뒤에 람다 표현식의 본문을 담는 중괄호 {}가 나온다.

                            
                                [캡쳐] (파라미터 선언) 지정자 -> 반환형 { 함수 본문 } (인수)
                            
                        

    1. 캡쳐 captures

    제일 앞의 대괄호 []를 캡쳐블록이라고 부른다. 대괄호안에 어떤 변수를 지정하면 람다 표현식의 본문에서 그 변수를 사용하게 만들수 있다. 이를 캡처한다고 표현한다. 아무 변수 넣지 않고 비워두면 속한 스코프에 있는 변수를 캡처하지 않는다. - [] : 비어있음. 캡쳐하지 않음 - = : 값에 의한 캡쳐, 모든 외부 변수를 캡쳐함./ 람다 식 안에서 수정할 수 없음 - & : 참조에 의한 캡쳐, 모든 외부 변수 캡쳐함 - <변수이름> : 특정 변수 값으로 캡처 / 람다 식 안에서 수정할 수 없음 - &<변수이름> : 특정 변수를 참조로 캡처

                            
                                1. captures(캡쳐)?
            
                                - []를 사용. 캡처하지 않는 경우
                                
                                //방법1 
                                auto noCapture = []() {std::cout<< "No Capture"<<std::endl;};
                                noCapture();
                                
                                //방법2 바로 실행
                                []() {std::cout<< "No Capture2"<<std::endl;};
                                - =를 사용.  값에 의한 캡쳐 (값 수정은 불가)
                                
                                int a = 10;
                                int b = 50;
                                
                                auto max = [=]() { return a > b ? a : b; };
                                
                                cout <<"Max is " << max() <<endl;
                                result :
                                
                                Max is 50
                                
                                
                                
                                
                                - &를 사용. 참조에 의한 캡쳐  (값 수정이 된다.)
                                
                                    int a = 10;
                                    int b = 50;
                                
                                    auto changeValue = [&]()
                                    {
                                        a = 20;
                                        b = 12;
                                    };
                                
                                    cout<<a<<" "<<b<<endl;
                                
                                    changeValue();
                                
                                    cout <<"result : " << a << " " << b << endl;
                                resutl :
                                
                                10 50
                                result : 20 12
                                
                                
                                - <변수이름>을 넣어 특정 변수값으로 캡쳐 ( 값 수정 불가)
                                
                                    int a = 10;
                                    int b = 50;
                                    int c = 20;
                                
                                    auto printValue = [a, b]()
                                    {
                                        cout << a << "/" << b << endl;
                                        //여기서 변수 c에 접근할 수 없음
                                    };
                                
                                    printValue();
                                result :
                                
                                10/50
                                
                                
                                -<&변수이름>을 넣어 특정 변수을 참조로 캡쳐
                                
                                    int a = 10;
                                    int b = 50;
                                    int c = 20;
                                
                                    auto changeValue = [&a]()
                                    {
                                        a = 100;
                                        //b와 c는 접근할 수 없음
                                    };
                                
                                    changeValue();
                                
                                    cout<< a<< endl;
                                100/50/20
                                이 외에도 캡쳐 옵션을 섞을 수도 있다.(캡처 리스트라고도 함)
                                
                                
                                
                                    int a = 10;
                                    int b = 50;
                                    int c = 20;
                                
                                    auto changeValue = [=, &a]()
                                    {
                                        a = 100;  //참조에 의한 캡쳐
                                        cout << b << endl; //값에 의한 캡쳐
                                    };
                                
                                    changeValue();
                                
                                    cout << a <<"/" << b <<"/" << c << endl;
                                50
                                100/50/20
                                
                                
                                2. parameters(파라미터) 
                                
                                선택사항이다.
                                
                                    auto noCapture = [] {std::cout << "No Capture" << std::endl; }; //빈 괄호를 생략 가능
                                    noCapture();
                                
                                    auto noCapture2 = []() {std::cout << "No Capture2!" << std::endl; };
                                    noCapture2();
                                예) 두 값 더하기 
                                
                                int scores1 = 6;
                                int scores2 = 23;
                                
                                auto add = [] (int a, int b)
                                {
                                    return a + b;
                                };
                                
                                cout<< add(scores1, scores2) <<endl;
                                3. specifiers (지정자)
                                
                                컴파일러는 람다 표현식을 이름 없는 펑터(함수 객체)로 변환한다. 캡처한 변수는 이 펑터의 데이터 멤버가 된다. 값으로 캡처한 변수는 펑터의 데이터 멤버로 복제 된다.
                                펑터마다 함수 호출 연산자인 operator()가 구현돼 있다. 람다 표현식의 경우 이 연산자는 기본적으로 const로 설정. 따라서 non-const 변수를 람다 표현식에 값으로 캡처해도 람다 표현식 안에서 이 값의 복제본을 수정 할 수 없다.
                                다음과 같이 람다 표현식을 mutable로 지정하면 함수호출 연산자를 non-const로 만들 수 있다.
                                mutable을 지정할때 매개변수가 없더라도 소괄호를 반드시 적어야 한다.
                                - 선택사항.
                                
                                - mutable
                                
                                1. 값에 의해 캡쳐된 개체를 수정할 수 있게함.
                                
                                2. 괜찮은 언어 디자인, 허나 C++에 너무 늦게 들어옴.
                                
                                int a = 10;
                                
                                
                                auto changeValue() = [a]() 
                                {
                                cout<<++a<<endl //컴파일 에러
                                };
                                
                                cout << changeValue() <<endl;
                                int a = 10;
                                
                                
                                auto changeValue() = [a]() mutable //추가
                                {
                                cout<< ++a <<endl //ok
                                };
                                
                                cout << changeValue() <<endl;
                                
                                
                                4. return_type(반환형)
                                
                                - 선택사항
                                
                                - 반환 형을 적지 않으면 컴파일러가 추론한다. 방법은 일반 함수의 리턴 타입을 추론할때와 같다.
                                
                                
                                
                                - std::function을 이용하여 함수가 람다 표현식을 리턴하게 만들 수 있다.
                                
                                function<int(void)> multiplyBy2Lambda(int x)
                                {
                                    return [x]{ return 2 * x};
                                }
                                물론 auto로도 쓸수 있다.
                                
                                auto fn =  multiplyBy2Lambda(5);
                                cout<< fn() <<endl; //결과는 10
                                
                                auto multiplyBy2Lambda(int x)
                                {
                                    return [x]{ return 2 * x};
                                }
                                
                                
                                람다 식의 장점
                                - 간단한 함수를 빠르게 작성할 수 있음.
                                
                                auto max = [](int a,int b) { return a > b ? a : b; };
                                
                                
                                람다 식의 단점
                                - 디버깅하기 힘들어짐. (콜스택 보기가 힘듦)
                                
                                - 함수 재사용성이 낮음
                                
                                - 사람들은 보통 함수를 새로 만들기 전에 클래스에 있는 기존 함수를 찾아봄
                                
                                - 람다 함수는 눈에 잘띄지 않아서 못찾을 가능성이 높음
                                
                                - 코드 중복 발생 할 여지가 있음.
                                
                                
                                
                                
                                
                                최적의 사용은?
                                
                                1. 기본적으로는 이름 있는 기본 함수를 쓰자.
                                
                                2. 자잘한 함수는 람다로 써도 괜찮다 ( 한줄짜리 함수)
                                
                                3. 허나 재사용할 수 있는 함수를 찾기 어렵다.
                                
                                4. 정렬함수처럼 STL컨테이너에 매개변수로 전달할 함수들도 좋은 후보이다.
                                
                                
                                
                                
                                
                                
                                
                                실질적으로 간단한 내용의 함수를 작성할때 쓰면 좋긴 하다.(ex. 1줄짜리의 내용 함수정두? 내 생각엔 5줄이하 까지는 무난하게 괜찮지 않을까 싶다.) 하지만, 선택은 본인 몫이고, 팀에서 사용하는 룰이 있다면 그 룰에 따르는게 좋은것같다.
                                
                                
                                
                                
    
                            
                        

    람다식의 필요성

    람다식을 안쓴다고 생각해보자. 그럼 특정한 기능을 묶은 함수의 연속을 묶어야하는데, 한번만 사용할 것이다. Lambda를 안쓴다고 한다면, 묶을 함수를 선언하고 정의해주어야 한다. 번거롭다. 람다식을 사용하면 선언과 정의없이 그냥 기능의 연속을 묶어서 비교적 간단하게 사용할수 있다.

    람다식의 장점

    * 간단한 함수를 빠르게 작성한다. * 재사용 성이 없고 함수화가 굳이 필요하지 않은 1회성 함수는 만들어서 사용하면 좋음. -인라인 함수처럼 함수 호출 과정을 생략하여 시간을 절약할 수 있습니다. -일반 함수의 경우 함수의 선언, 정의, 호출의 과정을 가지고 있는데요, 람다식은 이를 한번에 표현해서 시간을 절약할 수 있죠!

    람다식의 단점

    * 디버깅하기 힘들어진다.(Call Stack에서 함수안에서 함수 실행 위치를 알려줌) * 함수 재사용성과 가독성이 떨어진다. * 함수를 만들기 전에 보통 클래스에서 함수명을 찾아서 유지보수를 하는데 그럴 수가 없음 * 함수가 눈에 띄지 않기 때문에 똑같은 함수를 만들 가능성이 높음.

    구성

    [변수 캡쳐](받을 인자)->리턴타입{함수}(넘길 인자)

    []

    캡쳐

    람다 함수가 참조할 환경을 캡쳐(Capture)라고도 하는데, 우리 코드의 경우 람다 구문에서 인스턴스의 관련 멤버 함수와 변수를 사용하기 때문에 캡쳐환경을 this로 지정했다.

    [변수캡쳐]는 현재 함수에서 사용할 외부 변수들을 뜻함 main함수에 int num;이라는 변수가 있다면 그 변수를 사용하기 위해서는 변수 캡쳐를 사용하여야 한다 변수 캡쳐에 =를 넣으면 해당 함수의 모든 변수를 전부다 사용한다는 의미 &을 넣으면 모든 변수를 참조형으로 받아들인다는 의미이다 [=, &num]처럼 특정 변수만 참조형으로 사용하는 것도 가능 비워두면 아무것도 사용하지 않는다는 뜻 참고로 전역변수는 캡쳐를 해줄 필요가 없다

    ()

    Parameter List

    람다 함수가 사용할 파라미터를 지정하는 구문이다. 우리가 사용할 델리게이트는 함수 인자가 없으므로, 빈 괄호를 사용한다.

    ​부분은 말 그대로 함수에서 받는 인자들이다 일반적으로 int add(int a, int b); 선언할때 괄호 안에 있는 a,b 변수처럼 인자로 값을 받을 타입들을 지정해주는 것이다

    ->

    후행 반환 타입 (Trailing Return Type)

    '->' 기호를 사용한 후 람다 함수가 반환할 타입을 지정한다. 우리가 사용할 델리게이트는 반환값이 없으므로 void 를 사용한다. ​이것도 말 그대로 리턴해주는 타입을 지정해주는 것 void일경우 화살표와 함께 생략할 수 있다

    { }

    몸체영역

    { } 로 캡처 환경을 사용한 람다 함수의 로직을 넣어준다. 앞서 람다 소개자의 캡처를 this 로 지정했기 때문에, 멤버변수와 함수에 자유롭게 접근할 수 있다.

    ( )

    호출하는 함수에서 넘겨주는 값들이다 add(10, 20);으로 함수를 호출할때 10, 20을 넣어주는 것처럼 넣어주면 된다

                            
            class Chulsoo
            {
            public:
                int eat(int steakWeight)
            };
            
            int Chulsoo::eat(int steakWeight)
            {
                cout << "eat()::철수는" << steakWeight << "g 스테이크를 먹었다" << endl;
                return steakWeight;
            }
            
            int main()
            {
                Chulsoo chulsoo;
                chulsoo.eat(1000);
            
                return
            }
                            
                        
                            
            int main()
            {
                [](int steakWeight)
                {
                    cout << "eatLamda() :: 철수는" << steakWeight << "g 스테이크를 먹는다." << endl;
                }
                (1000);
            
                return 0;
            }                    
                            
                        
    참고 : https://namu.wiki/w/%EB%9E%8C%EB%8B%A4%EC%8B%9D