C++14: lambda 함수의 캡처 목록

C++11 lambda 함수에 값을 전달하는 방법은 복사/참조 두 가지 뿐이라서 expression 을 전달하진 못한다. lambda를 아주 거칠게 묘사하면 특정 인자를 받는 functor struct를 자동으로 생성하고 이거의 타입 추론을 자동으로 해주는 정도다. 그러니 생성자에 해당하는 lambda 캡처 목록에 “생성자에 해당하는게 있는데 왜 expression은 생성자에 못 넘기고, 변수 혹은 변수의 참조만 넘길까” 라고 생각하는건 매우 타당한 의문인 것.

예를 들어 C++11 에서 매우 거대한 std::unordered_map<k , v> 를 캡처한다고 치면 정말 복사를 해야 한다. 혹은 std::shared_ptr< ...> 를 써서 넘기거나, 아니면 “Effective Modern C++” 의 item 32에서 다루는 std::bind 트릭을 써야 한다.

unordered_map<int, string> m = large_object_factory();
// 타이머 이벤트를 건다. 수 초 후에 m 가지고 계산 수행
SetTimer(10000, [m]() {
   /* m 가지고 뭔가 수행 */
});

위처럼하면 m 을 통채로 복사해야해서 별로 효율적이지 않다. std::shared_ptr 를 쓰면,

auto pm = make_shared<unordered_map<int, string>>( \
    large_object_factory());
// 타이머 이벤트를 건다. 수 초 후에 m 가지고 계산 수행
SetTimer(10000, [pm]() {
   /* pm 가지고 뭔가 수행 */
});

shared_ptr 생성/복사 오버헤드 정도로 처리할 수 있다. 위 코드가 동작은 하지만, 간결하진 않다. C++03 에서 C++1x 로 넘어올 때의 간결해진 코드 베이스를 생각하면 정말 맘에 안든다.

그래서 캡처 문법을 일반화해서 C++14 에는 lambda 구문에 capture init-list 라는 개념이 생겼다. 해당 드래프트에서는 generalized lambda capture 라고 부르는데, up-value를 캡처하는게 변수 이름만으로 하는게 아니라 일반적인 expression 을 다룰 수 있게 되었기 때문. expression 이 되니까 std::move 를 써서 값을 옮겨버리는 것도 가능하다.

즉, 다음과 같은 코드가 가능해진다. 잘 보면 생성자처럼도 보인다. (?)

unordered_map<int, string> m = large_object_factory();
// 타이머 이벤트를 건다. 수 초 후에 m 가지고 계산 수행
SetTimer(10000, [_m = std::move(m)]() {
   /* _m 가지고 뭔가 수행 */
});

즉, C++11 에서는 혼용할 수 없는 lambda 캡처 목록에서 expression 사용이 가능해진 것 (즉, rvalue 참조를 써서 효율적으로 복사하는게 가능해진 것).

Published by

rein

나는 ...

2 thoughts on “C++14: lambda 함수의 캡처 목록”

  1. 재밌게 잘 봤습니다. 궁금한게 있는데요. 왜 m을 &m로 캡쳐하면 안 되나요? SetTime 호출 뒤 범위를 벗어나 m이 파괴되나요? 또, 람다 캡처리스트 말고 인자 리스트로는 넣을 수 없는 상황인가요?

    1. 해당 함수 이름에서 “SetTimer”가 일정 시간 후에 호출되는 타이머 큐 구현으로 가정하시면 됩니다. (즉, 지금 보이는 스택 프레임이 없어진 후에 호출합니다)
      그래서 m을 참조로 캡처할 수 없습니다. 비슷한 이유로, 실제로 타이머를 실행하는 곳에서 인자로 뭘 넘길 순 없습니다. (그리고 뭘 넘기려고 해도 구현이 비직교적이 될테니 별로 좋은 방향이 아닙니다)

Leave a Reply