이 질문은 코드에서 완전히 대답 할 수 없습니다. 다소 "동등한"코드를 작성할 수 있지만 표준은 그런 식으로 지정되지 않습니다.
그 길을 벗어나서로 들어가 보자 [expr.prim.lambda]
. 가장 먼저 주목할 점은 생성자에 대해서만 언급된다는 것입니다 [expr.prim.lambda.closure]/13
.
lambda-expression 에 lambda-capture가 있고 그렇지 않으면 기본 기본 생성자 가 있으면 lambda-expression 과 연관된 클로저 유형에 기본 생성자가 없습니다 . 기본 복사 생성자와 기본 이동 생성자가 있습니다 ([class.copy.ctor]). lambda-expression 에 lambda-capture가 있고 기본적으로 copy 및 move 대입 연산자 가있는 경우 삭제 된 대입 연산자가 있습니다 ([class.copy.assign]). [ 참고 : 이러한 특수 멤버 함수는 일반적으로 암시 적으로 정의되므로 삭제 된 것으로 정의 될 수 있습니다. — 끝 참고 ]
따라서 바로 생성자에서 공식적으로 객체 캡처 방법이 정의되지 않았 음을 분명히해야합니다. 꽤 가까이 갈 수는 있지만 (cppinsights.io 답변 참조) 세부 사항은 다릅니다 (사례 4에 대한 해당 답변의 코드가 어떻게 컴파일되지 않는지 참고).
사례 1을 논의하는 데 필요한 주요 표준 조항은 다음과 같습니다.
[expr.prim.lambda.capture]/10
[...]
사본으로 캡처 된 각 엔티티에 대해 이름이 지정되지 않은 비 정적 데이터 멤버가 클로저 유형으로 선언됩니다. 이 멤버의 선언 순서는 지정되어 있지 않습니다. 엔티티가 오브젝트에 대한 참조 인 경우 이러한 데이터 멤버의 유형이 참조 된 유형이고, 엔티티가 함수에 대한 참조 인 경우 참조 된 함수 유형에 대한 lvalue 참조이거나 그렇지 않은 경우 해당 캡처 된 엔티티의 유형입니다. 익명의 노조원은 사본으로 캡처 할 수 없습니다.
[expr.prim.lambda.capture]/11
copy에 의해 캡처 된 엔티티의 odr-use 인 lambda-expression 의 compound-statement 내의 모든 id-expression 은 클로저 유형의 해당 이름이없는 데이터 멤버에 대한 액세스로 변환됩니다. [...]
[expr.prim.lambda.capture]/15
람다-표현식이 평가 될 때, 복사에 의해 캡처 된 엔티티는 결과 클로저 오브젝트의 각각의 대응하는 비 정적 데이터 멤버를 직접 초기화하는데 사용되며, 초기화 캡처에 대응하는 비 정적 데이터 멤버는 다음과 같이 초기화된다. 해당 초기화 프로그램에 의해 표시됩니다 (복사 또는 직접 초기화 일 수 있음). [...]
이것을 당신의 사건 1에 적용합시다 :
사례 1 : 값으로 캡처 / 값으로 기본 캡처
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
이 람다의 클로저 타입은 이름이 지정되지 않은 비 정적 데이터 멤버 (이것이라고 부르 자 __x
)를 가질 것이며 ( 참조 나 함수가 아니기 int
때문에 x
) x
람다 바디 내에서의 접근은로 접근하도록 변환된다 __x
. 람다 식을 평가할 때 (즉에 할당 할 때 lambda
)로 직접 초기화 __x
합니다 x
.
즉, 하나의 사본 만 발생 합니다. 클로저 타입의 생성자는 포함되지 않으며, 이것을 "정상"C ++로 표현할 수 없습니다 (클로저 타입 도 집계 타입이 아닙니다 ).
참조 캡처에는 [expr.prim.lambda.capture]/12
다음이 포함됩니다 .
엔티티가 내재적으로 또는 명시 적으로 캡처되었지만 사본으로 캡처되지 않은 경우 참조로 캡처됩니다. 추가로 명명되지 않은 비 정적 데이터 멤버가 참조로 캡처 된 엔티티의 클로저 유형으로 선언되는지 여부는 지정되지 않았습니다. [...]
참조의 참조 캡처에 대한 또 다른 단락이 있지만 우리는 그것을 어디서나하지 않습니다.
따라서 사례 2 :
사례 2 : 참조로 캡처 / 참조로 기본 캡처
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
멤버가 클로저 유형에 추가되는지 여부는 알 수 없습니다. x
람다 몸에 그냥 x
외부 를 직접 참조 할 수 있습니다 . 이것은 컴파일러에게 달려 있으며 C ++ 코드의 소스 변환이 아닌 중간 언어의 형태 (컴파일러와는 다른) 로이 작업을 수행합니다.
초기화 캡처는 다음에 자세히 설명되어 있습니다 [expr.prim.lambda.capture]/6
.
init-capture auto init-capture ;
는 선언 영역이 람다-표현식의 복합 문인 형식 의 변수를 선언하고 명시 적으로 캡처하는 것처럼 동작 합니다.
- (6.1) 캡처가 복사에 의한 경우 (아래 참조) 캡처에 대해 선언 된 비 정적 데이터 멤버 및 변수는 비 정적 데이터 수명이있는 동일한 객체를 참조하는 두 가지 다른 방법으로 처리됩니다. 추가 복사 및 파기는 수행되지 않으며
- (6.2) 캡처가 참조 인 경우 클로저 객체의 수명이 끝나면 변수의 수명이 끝납니다.
이를 고려하여 사례 3을 살펴 보겠습니다.
사례 3 : 일반화 된 초기화 캡처
auto lambda = [x = 33]() { std::cout << x << std::endl; };
언급했듯이 이것을 auto x = 33;
복사하여 명시 적으로 캡처 하는 변수로 생각하십시오 . 이 변수는 람다 본문 내에서만 "표시"됩니다. 앞서 언급 한 바와 같이 [expr.prim.lambda.capture]/15
, 클로저 타입의 대응 멤버 ( __x
후손을위한)의 초기화는 람다 식의 평가시 주어진 이니셜 라이저에 의해 이루어진다.
의심의 여지를 피하기 위해 : 여기서 물건이 두 번 초기화되는 것은 아닙니다. 는 auto x = 33;
에 "것과"단순 캡처의 의미를 계승하고, 상술 한 초기화 그 의미의 변형이다. 한 번의 초기화 만 발생합니다.
여기에는 사례 4도 포함됩니다.
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
클로저 타입 멤버는 __p = std::move(unique_ptr_var)
람다식이 평가 될 때 (즉, l
할당 될 때 )에 의해 초기화된다 . p
람다 본문에 대한 액세스는 에 대한 액세스로 변환됩니다 __p
.
TL; DR : 최소한의 복사 / 초기화 / 이동 만 수행됩니다 (원하는대로). 람다는 소스 변환 (다른 구문 설탕과 달리)으로 정확하게 지정 되지 않았다고 가정합니다 . 생성자 측면에서 물건을 표현하면 불필요한 연산이 필요 하기 때문 입니다.
나는 이것이 질문에 표현 된 두려움을 해결하기를 바랍니다 :)