Lambda (람다)

1. Lambda

한달간 서버 제작을 하면서 참 많이 사용한 것중에 하나가 이 Lambda다. 이거에 한 번 맛을 들이니 빠져나올 수가 없다...;

1.1. 함수 객체

람다를 만들기 이전에 함수 객체라는 것이 있다. 람다는 함수 객체의 확장판이라고 보면 될 것같다.

그럼 이 함수 객체란 무엇이냐?

함수 객체는 객체를 마치 함수처럼 사용하기에 붙여진 이름이고 함수가 되기위한 조건중 하나는 괄호인데, (함수 호출 연산자 Function call operator)를 사용해 파라미터 목록을 받는 것이다. 어떤 객체를 함수처럼 사용한다는 것은 객체에 괄호를 붙여서 마치 겉보기엔 함수를 호출하는 것처럼 사용한다는 의미이다.
출처: http://vallista.tistory.com/entry/C-11-Lambda-Expression-람다-표현식-함수-객체-Functor [VallistA]

즉, ()연산자 오버로딩을 이용해서, 겉보기엔 함수를 호출하는 것처럼 사용한다는 의미.

1.1.1. 예시

class Functor{
        void operator()(){
            cout << "Functor::operator()" >> endl;
        }
};

int main(){
    Functor fun;
    fun();
}

1.1.2. 함수 객체의 중요성

왜 굳이 함수 객체를 써야하는가? 많은 이유가 있겠지만, 나는 두 가지 이유로 자주 쓴다.

  1. 콜백 함수 구현
  2. 함수의 구현을 다른 객체에 맡긴다.

1의 경우엔 콜백 함수를 쓰면 편해진다. 함수 객체를 쓰지 않으면 굉장히 몇 번은 꼬아야할 부분도 이거 한 방이면 상당히 많이 풀린다. 물론 너무 많이 쓰면 머리가 아프긴하다..마치 스레드처럼 말이다.

2의 경우엔 원칙을 정해놓고 쓴다. 함수의 구현을 다른 객체에 맡긴다는건 위험하니까. 나의 경우엔,

  1. 컨테이너같은 객체의 집합에 대한 일괄적인 명령을 내릴 때

이 경우에만 쓴다.

그리고 다른 이유로 STL의 알고리즘을 보기 위해서다. STL 알고리즘은 함수 객체를 많이 쓴다(고 한다.). 사실 STL 알고리즘을 직접 본 적은 없지만..일단 함수 객체를 많이 쓸 것같긴하다.

1.2. Lambda란?

람다는 함수 객체와 목적이 동일하면서 좀 더 발전된 형태다. 함수 객체보다는 코딩이 간편해지고 가독성이 높아진다. 함수 객체이면서 () 연산자 오버로딩을 정의할 필요가 없으니 더 좋아질만하다.

1.2.1. 예시

int main() {
    std::vector<int> vec = {1,2,3,4,5};
    int find_num = 3;
    
    auto result = ::find_if(vec.begin(), vec.end(), [&](int i)->bool{return i == find_num;});
}

1.2.2. Lambda 캡처

이 캡처라는 것이 상당히 이상하다. 적어도 내가 보기엔 말이다. 내부 함수가 외부 함수에 접근이 할 수있게 해준다. 위의 예시에서도 볼 수 있듯이 람다 함수에서 외부 함수의 값인 find_num에 접근하고 있다. 어떻게 접근하는지는 제쳐두고, 람다는 외부 함수의 값에 접근이 가능하고, 사용방법을 말하겠다.

[] 는 캡처 절 이라고 하는데, 람다 함수에서 캡처의 대상은 람다 함수 외부에 선언된 (람다 함수를 감싸는 스코프 범위안의) 변수 이다.

[] : 아무것도 캡처하지 않음
[&x]: x만 Capture by reference
[x] : x만 Capture by value
[&] : 모든 외부 변수를 Capture by reference
[=] : 모든 외부 변수를 Capture by value
[x,y] : x,y 를 Capture by value
[&x,y] : x는 Capture by reference , y는 Capture by value
[&x, &y] : x,y 를 Capture by reference
[&, y] : y 를 제외한 모든 값을 Capture by reference
[=, &x] : x 를 제외한 모든 값을 Capture by value

참고로 전역 변수는 캡처할 수 없다. 값으로 캡쳐된 외부 변수는 const의 특성을 가지게 되어, 람다 함수 몸체에서 값에대한 수정이 불가능해진다.

이 캡쳐 방식을 잘 기억해야하는 이유는 객체가 캡처될 때, 복사 생성이 일어나기 때문이다.

class Function {
public:
    Function() {cout << "Function()" << endl; }
    Function(const Function&) {cout << "Function(Function&)" << endl;}
    ~Function() { cout << "~Function()" << endl; }

    Function& operator=(Function&) { cout << "Function::operator=(Function&)" << endl; }

    int m_a;
};

int main() {
    Function t;

    auto lam = [=]() {cout << t.m_a << endl;};
}

이 코드를 실행시키면 복사생성자가 호출되는 것을 볼 수 있다.

'프로그래밍 > 프로그래밍 관련' 카테고리의 다른 글

UML을 살펴보자  (0) 2019.01.13
std::tuple을 알아보자.  (0) 2018.04.26
함수 포인터로 함수명 변경하기 (완결)  (0) 2018.04.24
Epoll  (0) 2018.03.22
함수포인터로 함수명 변경하기  (0) 2018.03.22
더보기

댓글,

Lowpoly

게임 서버 프로그래머 지망생