종종 값 캡쳐모드나 참조 캡쳐모드 둘 다 사용이 마땅치 않은 경우가 있다. 예를 들어 이동 전용 객체(unique_ptr, future 등)를 클로저 안으로 들여오려는 경우이다.
C++11에서는 방법이 없지만 C++14에서는 초기화 캡쳐라는것을 지원함으로써 가능해졌다. 초기화 캡쳐는 기본 캡쳐모드를 표현할 수 없지만 애초에 직전 항목에서 언급했다시피 기본 캡쳐모드는 피해야 하기 때문에 큰 의미가 없다.
초기화 캡쳐는 클로저 클래스에 속한 자료 멤버의 이름과 그 자료 멤버를 초기화하는 표현식을 지정할 수 있다.
class Widget {
...
};
auto pw = std::make_unique<Widget>();
auto func = [pw = std::move(pw)]{ ... };
캡쳐 부분에서 '=' 를 기준으로 좌변은 클로저 클래스의 자료 멤버, 우변은 초기화 표현식이다.
둘다 이름이 같지만 좌변과 우변의 범위가 다르다. 좌변은 클로저 클래스의 범위이고 우변은 람다가 정의되는 지점의 범위와 동일하다.
auto func = [pw = std::make_unique<Widget>()]{ ... };
별다른 사용 없이 바로 클로저에서 사용할거라면 위와 같이 초기화도 가능하다.
C++11에서는 어떤 표현식의 결과를 캡쳐하는것이 불가능했지만 C++14는 가능해졌다.
만약 C++11에서 이동 캡쳐를 구현하고 싶다면 방법이 존재한다.
캡쳐할 객체를 std::bind가 산출하는 함수 객체로 이동하고 캡쳐된 객체에 대한 참조를 람다에 넘겨주면 된다.
std::vector<double> data;
auto func = [data = std::move(data)]() { ... }; // C++14
auto func = std::bind([](const std::vector<double>& data) { ... }, std::move(data)); // C++11 우회
bind에 의해 함수 객체가 산출되고(바인드 객체) 바인드 객체가 호출되면 이동 생성된 data의 복사본이 람다에 전달된다.
data는 move에 의해 오른값이 되었지만 람다의 매개변수가 왼값 참조이기 때문에 이동 생성된 data의 복사본이 전달되게된다.
기본적으로 람다로부터 만들어진 클로저 클래스의 operator() 멤버 함수는 const이다. 그래서 람다 본문 안에서 클로저의 모든 자료 멤버는 const가 되지만 이동 생성된 data의 복사본은 const가 아니다.
변경 가능한 람다를 사용하고 싶다면 mutable로 선언하면 된다. 이 경우 매개변수의 const 한정자도 없애줘야 한다.
바인드 객체는 전달된 모든 인수의 복사본을 저장하므로 클로저와 바인드 객체의 수명은 동일하다.
사실 std::bind보다 람다를 선호하는 편이 좋다. C++14 이상이라면 맘 편하게 초기화 캡쳐를 사용하면 된다.
◾ 객체를 클로저 안으로 이동할 때에는 C++14의 초기화 캡쳐를 사용하라.
◾ C++11에서는 직접 작성한 클래스나 std::bind로 초기화 캡쳐를 흉내 낼 수 있다.
'도서 > Effective Modern C++' 카테고리의 다른 글
[6장] 항목 34 : std::bind보다 람다를 선호하라 (0) | 2023.01.29 |
---|---|
[6장] 항목 33 : std::forward를 통해서 전달할 auto&& 매개변수에는 decltype을 사용하라 (0) | 2023.01.27 |
[6장] 항목 31 : 기본 캡쳐 모드를 피하라 (0) | 2023.01.27 |
[5장] 항목 30 : 완벽 전달이 실패하는 경우들을 잘 알아두라 (0) | 2023.01.27 |
[5장] 항목 29 : 이동 연산이 존재하지 않고, 저렴하지 않고, 적용되지 않는다고 가정하라 (0) | 2023.01.25 |