Câu hỏi này không thể được trả lời đầy đủ trong mã. Bạn có thể viết mã "tương đương", nhưng tiêu chuẩn không được chỉ định theo cách đó.
Với cách đó, hãy đi sâu vào [expr.prim.lambda]
. Điều đầu tiên cần lưu ý là các hàm tạo chỉ được đề cập trong [expr.prim.lambda.closure]/13
:
Kiểu đóng được liên kết với biểu thức lambda không có hàm tạo mặc định nếu biểu thức lambda có hàm lambda và hàm tạo mặc định mặc định khác. Nó có một hàm tạo sao chép mặc định và hàm tạo di chuyển mặc định ([class.copy.ctor]). Nó có một toán tử gán bản sao xóa nếu lambda thể hiện có một lambda-chụp và sao chép defaulted và toán tử gán di chuyển khác ([class.copy.assign]). [ Lưu ý: Các hàm thành viên đặc biệt này được định nghĩa ngầm như bình thường và do đó có thể được xác định là đã xóa. - lưu ý cuối ]
Vì vậy, ngay lập tức con dơi, cần phải rõ ràng rằng các nhà xây dựng không chính thức làm thế nào để bắt các đối tượng được xác định. Bạn có thể nhận được khá gần (xem câu trả lời cppinsights.io), nhưng các chi tiết khác nhau (lưu ý cách mã trong câu trả lời đó cho trường hợp 4 không biên dịch).
Đây là những mệnh đề tiêu chuẩn chính cần thiết để thảo luận về trường hợp 1:
[expr.prim.lambda.capture]/10
[...]
Đối với mỗi thực thể được bắt bởi bản sao, một thành viên dữ liệu không tĩnh không được đặt tên được khai báo theo kiểu đóng. Trình tự khai báo của các thành viên này là không xác định. Loại thành viên dữ liệu đó là loại được tham chiếu nếu thực thể là tham chiếu đến một đối tượng, tham chiếu giá trị cho loại hàm được tham chiếu nếu thực thể là tham chiếu đến hàm hoặc loại thực thể bị bắt tương ứng. Một thành viên của một liên minh ẩn danh sẽ không bị bắt bởi bản sao.
[expr.prim.lambda.capture]/11
Mọi biểu thức id trong câu lệnh ghép của biểu thức lambda là sử dụng odr của một thực thể được bắt bởi bản sao được chuyển thành quyền truy cập vào thành viên dữ liệu chưa được đặt tên tương ứng của kiểu đóng. [...]
[expr.prim.lambda.capture]/15
Khi biểu thức lambda được ước tính, các thực thể được bắt giữ bằng bản sao được sử dụng để khởi tạo trực tiếp từng thành viên dữ liệu không tĩnh tương ứng của đối tượng đóng kết quả và các thành viên dữ liệu không tĩnh tương ứng với các bắt giữ init được khởi tạo như được chỉ định bởi trình khởi tạo tương ứng (có thể là sao chép hoặc khởi tạo trực tiếp). [...]
Hãy áp dụng điều này cho trường hợp của bạn 1:
Trường hợp 1: chụp theo giá trị / chụp mặc định theo giá trị
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Kiểu đóng của lambda này sẽ có một thành viên dữ liệu không tĩnh không tên (gọi nó là __x
) loại int
(vì x
không phải là tham chiếu cũng không phải là hàm) và truy cập vào x
bên trong cơ thể lambda được chuyển đổi thành quyền truy cập __x
. Khi chúng ta đánh giá biểu thức lambda (tức là khi gán cho lambda
), chúng ta khởi tạo trực tiếp __x
với x
.
Nói tóm lại, chỉ có một bản sao diễn ra . Hàm tạo của kiểu đóng không liên quan và không thể biểu thị điều này trong C ++ "bình thường" (lưu ý rằng kiểu đóng cũng không phải là kiểu tổng hợp ).
Chụp tham chiếu liên quan đến [expr.prim.lambda.capture]/12
:
Một thực thể được chụp bằng tham chiếu nếu nó được chụp ngầm hoặc rõ ràng nhưng không bị bắt bởi bản sao. Không xác định liệu các thành viên dữ liệu không tĩnh không được đặt tên bổ sung có được khai báo theo kiểu đóng cho các thực thể được bắt bởi tham chiếu hay không. [...]
Có một đoạn khác về chụp tham chiếu các tài liệu tham khảo nhưng chúng tôi không làm điều đó ở bất cứ đâu.
Vì vậy, đối với trường hợp 2:
Trường hợp 2: chụp theo tham chiếu / chụp mặc định bằng tham chiếu
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Chúng tôi không biết liệu một thành viên được thêm vào loại đóng cửa. x
trong cơ thể lambda có thể chỉ trực tiếp đề cập đến x
bên ngoài. Điều này tùy thuộc vào trình biên dịch, và nó sẽ thực hiện điều này trong một số dạng ngôn ngữ trung gian (khác với trình biên dịch sang trình biên dịch), không phải là một chuyển đổi nguồn của mã C ++.
Ban đầu chụp được chi tiết trong [expr.prim.lambda.capture]/6
:
Một init-Capture hoạt động như thể nó tuyên bố và nắm bắt rõ ràng một biến auto init-capture ;
có dạng có vùng khai báo là câu lệnh ghép của biểu thức lambda, ngoại trừ:
- (6.1) nếu việc chụp là bằng bản sao (xem bên dưới), thành viên dữ liệu không tĩnh được khai báo cho việc chụp và biến được coi là hai cách khác nhau để tham chiếu đến cùng một đối tượng, có thời gian tồn tại của dữ liệu không tĩnh thành viên, và không có bản sao và phá hủy bổ sung được thực hiện, và
- (6.2) nếu việc chụp là do tham chiếu, thời gian tồn tại của biến sẽ kết thúc khi thời gian tồn tại của đối tượng đóng kết thúc.
Cho rằng, hãy xem trường hợp 3:
Trường hợp 3: Chụp init tổng quát
auto lambda = [x = 33]() { std::cout << x << std::endl; };
Như đã nêu, hãy tưởng tượng đây là một biến được tạo bởi auto x = 33;
và được ghi lại rõ ràng bằng bản sao. Biến này chỉ "hiển thị" trong cơ thể lambda. Như đã lưu ý [expr.prim.lambda.capture]/15
trước đó, việc khởi tạo thành viên tương ứng của kiểu đóng ( __x
đối với hậu thế) là do trình khởi tạo đã cho khi đánh giá biểu thức lambda.
Để tránh nghi ngờ: Điều này không có nghĩa là mọi thứ được khởi tạo hai lần ở đây. Đây auto x = 33;
là một "như thể" để kế thừa ngữ nghĩa của các ảnh chụp đơn giản và khởi tạo được mô tả là một sửa đổi cho các ngữ nghĩa đó. Chỉ có một khởi tạo xảy ra.
Điều này cũng bao gồm trường hợp 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
Thành viên kiểu đóng được khởi tạo bởi __p = std::move(unique_ptr_var)
khi biểu thức lambda được ước tính (nghĩa là khi l
được gán cho). Truy cập vào p
trong cơ thể lambda được chuyển thành truy cập vào __p
.
TL; DR: Chỉ có số lượng bản sao / khởi tạo / di chuyển tối thiểu được thực hiện (như người ta hy vọng / mong đợi). Tôi sẽ giả định rằng lambdas không được chỉ định theo cách chuyển đổi nguồn (không giống như đường cú pháp khác) chính xác bởi vì việc diễn đạt mọi thứ theo các nhà xây dựng sẽ đòi hỏi các hoạt động không cần thiết.
Tôi hy vọng điều này giải quyết những nỗi sợ hãi thể hiện trong câu hỏi :)