32. 객체를 클로저 안으로 이동하려면 초기화 캡쳐 (갈무리)를 사용하라
값 캡쳐, 참조 캡쳐 모두 사용하기 애매한 경우가 있다.
이동 전용 객체 (ex. std::unique_ptr )를 클로저 안으로 들여올 때가 그렇다.
C++11 에서는 방법이 없지만. C++14 에서는 객체를 클로저 안에 이동하는 기능을 제공한다.
Init Capture (초기화 캡쳐) 방식이 있는데 기본 캡쳐 모드 외에는 C++11 캡쳐 모드에서 지원하는 기능 전부 지원한다.
초기화 캡쳐는 다음과 같은 항목 지정이 가능하다.
- 람다로부터 생성되는 클로저 클래스의 자료 멤버 이름
- 해당 자료 멤버 초기화 표현식
예시)
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++ 버전으로 인해, 초기화 캡쳐를 사용할 수 없는 경우도 있다.
이 때 클래스를 직접 만드는 것도 방법이다. (람다가 할 수 있는 모든 기능은, 클래스를 생성해서도 구현할 수 있기 때문)
또한 람다를 사용하면서 이동 캡쳐를 흉내내는 방법도 있는데, 사용법은 다음과 같다.
- 캡쳐할 객체를 std::bind 가 산출하는 함수 객체로 이동
- 그 캡쳐된 객체에 대한 참조를 람다에 넘겨줌
예시)
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 이상 버전에서는 되도록 람다 및 초기화 캡쳐 기능을 사용하자.
'dev > C++' 카테고리의 다른 글
Effective Modern C++ #34 std::bind 보다 람다를 선호하라 (0) | 2025.06.30 |
---|---|
Effective Modern C++ #33 std::forward 를 통해서 전달할 auto&& 매개변수에는 decltype 을 사용하라 (0) | 2025.06.30 |
Effective Modern C++ #31 기본 Capture mode를 피해라 (0) | 2025.06.30 |
Effective Modern C++ #30 완벽 전달이 실패하는 경우들을 잘 알아두라 (0) | 2025.06.30 |
Effective Modern C++ #29 이동 연산이 존재하지 않고, 저렴하지 않고, 적용되지 않는다고 가정하라. (0) | 2025.06.28 |