dev/C++

Effective Modern C++ #32 객체를 클로저 안으로 이동하려면 초기화 캡쳐 (갈무리)를 사용하라

dev_dev 2025. 6. 30. 19:21

32. 객체를 클로저 안으로 이동하려면 초기화 캡쳐 (갈무리)를 사용하라

값 캡쳐, 참조 캡쳐 모두 사용하기 애매한 경우가 있다.

이동 전용 객체 (ex. std::unique_ptr )를 클로저 안으로 들여올 때가 그렇다.

C++11 에서는 방법이 없지만. C++14 에서는 객체를 클로저 안에 이동하는 기능을 제공한다.

Init Capture (초기화 캡쳐) 방식이 있는데 기본 캡쳐 모드 외에는 C++11 캡쳐 모드에서 지원하는 기능 전부 지원한다.

초기화 캡쳐는 다음과 같은 항목 지정이 가능하다.

  1. 람다로부터 생성되는 클로저 클래스의 자료 멤버 이름
  2. 해당 자료 멤버 초기화 표현식

예시)

auto pw = std::make_unique<Widget>();
...

auto func = [pw = std::move(pw)] {
	return pw->isValidated();
}

초기화 캡쳐 구문은 [pw = std::move(pw)] 이다.

 

좌변은 클로저 클래스 내부에서 사용할 자료 멤버 이름이다. (클로저 클래스 내부의 scope)

우변은 이를 초기화하는 표현식이다. ( 람다가 정의되는 지점의 scope)

 

C++11 과 다르게, C++14 에서는 위와 같이 어떤 표현식의 결과를 캡쳐할 수가 있다.

따라서 이를 Generalized Lambda Capure 라고 부른다.

 

컴파일러 또는 C++ 버전으로 인해, 초기화 캡쳐를 사용할 수 없는 경우도 있다.

이 때 클래스를 직접 만드는 것도 방법이다. (람다가 할 수 있는 모든 기능은, 클래스를 생성해서도 구현할 수 있기 때문)

또한 람다를 사용하면서 이동 캡쳐를 흉내내는 방법도 있는데, 사용법은 다음과 같다.

  1. 캡쳐할 객체를 std::bind 가 산출하는 함수 객체로 이동
  2. 그 캡쳐된 객체에 대한 참조를 람다에 넘겨줌

예시)

auto func = std::bind([](const std::vector<double>& data){/* data 사용 */}, std::move(data));

std::bind 가 리턴하는 함수 객체를 바인드 객체라고 부른다.

첫 인자는 호출 가능한 객체, 다음 인자는 그 객체에 전달할 값이다.

 

바인드 객체에는 std::bind 에 전달된 모든 인자가 존재한다.

l-value 인자에 대해 복사 생성된 객체, r-value 인자에 대해 이동 생성된 객체 등이 있다.

이동 생성된 객체가 바인드 객체 내부로 이동함에 따라, 기존 C++11 람다 캡쳐에서는 불가능했던 (객체를 클로저에 이동) 한계를 우회한다.

 

C++14 초기화 캡쳐와 다른 점은, 바인드 객체 내부에서 인자 사용 시, 이동 생성된 data 의 복사본 (l-value )를 사용하게 된다.

또한 클로저 클래스의 operator() 멤버 함수는 디폴트 const 이나, 바인드 객체 안에 이동 생성된 data 복사본은 const 가 아니다.

따라서 필요에 따라 매개변수를 const 로 참조하는 등 방식으로 활용해야한다.

 

초기화 캡쳐 대신 바인드 객체를 사용하는 경우, 아래 3 가지 요점을 따른다.

  • 객체를 C++11 클로저 안으로 이동 생성하는 것은 불가능하나, 객체를 C++11 바인드 객체 안으로 이동 생성하는 것은 가능함
  • C++11 에서 이동 캡쳐를 흉내내는 방법은 객체를 바인드 객체 안으로 이동 생성하고, 이동 생성된 객체를 람다에 참조로 전달하는 것
  • 바인드 객체 수명이 클로저 수명과 같으므로, 바인드 객체 안의 객체는 마치 클로저 안에 있는 것처럼 취급이 가능

초기화 캡쳐와 std::bind 사용 예시)

auto func = [pw = std::make_unique<Widget>()]{ return pw->isValidated(); };

auto func = std::bind([](const std::unique_ptr<Widget>& pw){ return pw->isValidated(); }, std::make_unique<Widget>());

C++11 람다의 한계를 우회하기 위해 std::bind 를 사용하는 것은 다소 모순적이다.

#34에서는 std::bind 대신 람다를 선호하라고 조언하기 때문이다.

따라서 C++14 이상 버전에서는 되도록 람다 및 초기화 캡쳐 기능을 사용하자.