Lambda được đề cập thực sự không có trạng thái .
Xem xét:
struct lambda {
auto operator()() const { return 17; }
};
Và nếu chúng ta có lambda f;
, nó là một lớp trống. Ở trên không chỉ giống về mặt lambda
chức năng với lambda của bạn, mà (về cơ bản) cách lambda của bạn được triển khai! (Nó cũng cần một toán tử con trỏ hàm ẩn cho hàm và tên lambda
sẽ được thay thế bằng một số giả hướng dẫn do trình biên dịch tạo ra)
Trong C ++, các đối tượng không phải là con trỏ. Chúng là những thứ thực tế. Họ chỉ sử dụng hết dung lượng cần thiết để lưu trữ dữ liệu trong đó. Một con trỏ tới một đối tượng có thể lớn hơn một đối tượng.
Mặc dù bạn có thể nghĩ lambda đó như một con trỏ đến một hàm, nhưng không phải vậy. Bạn không thể gán lại hàm auto f = [](){ return 17; };
cho một hàm hoặc lambda khác!
auto f = [](){ return 17; };
f = [](){ return -42; };
trên là bất hợp pháp . Không có chỗ ở f
để lưu trữ mà chức năng sẽ được gọi là - đó thông tin được lưu trữ trong các loại của f
, không tính vào giá trị của f
!
Nếu bạn đã làm điều này:
int(*f)() = [](){ return 17; };
hoặc cái này:
std::function<int()> f = [](){ return 17; };
bạn không còn lưu trữ lambda trực tiếp nữa. Trong cả hai trường hợp này, f = [](){ return -42; }
đều hợp pháp - vì vậy trong những trường hợp này, chúng tôi đang lưu trữ hàm mà chúng tôi đang gọi trong giá trị của f
. Và sizeof(f)
không còn nữa 1
, mà là sizeof(int(*)())
hoặc lớn hơn (về cơ bản, có kích thước con trỏ hoặc lớn hơn, như bạn mong đợi. std::function
Có kích thước tối thiểu được ngụ ý theo tiêu chuẩn (chúng phải có khả năng lưu trữ các vùng gọi "bên trong chính chúng" lên đến một kích thước nhất định) ít nhất là lớn bằng một con trỏ hàm trong thực tế).
Trong int(*f)()
trường hợp này, bạn đang lưu trữ một con trỏ hàm đến một hàm hoạt động như thể bạn đã gọi lambda đó. Điều này chỉ hoạt động cho lambdas không trạng thái (những người có []
danh sách chụp trống ).
Trong std::function<int()> f
trường hợp này, bạn đang tạo một phiên bản lớp xóa kiểu std::function<int()>
(trong trường hợp này) sử dụng vị trí mới để lưu trữ bản sao của lambda size-1 trong bộ đệm nội bộ (và nếu một lambda lớn hơn được chuyển vào (với nhiều trạng thái hơn ), sẽ sử dụng phân bổ đống).
Theo dự đoán, những thứ như thế này có thể là những gì bạn nghĩ đang diễn ra. Lambda đó là một đối tượng có kiểu được mô tả bằng chữ ký của nó. Trong C ++, người ta quyết định làm cho lambdas bằng không chi phí trừu tượng so với việc triển khai đối tượng hàm thủ công. Điều này cho phép bạn chuyển lambda vào một std
thuật toán (hoặc tương tự) và nội dung của nó hiển thị đầy đủ với trình biên dịch khi nó khởi tạo mẫu thuật toán. Nếu lambda có kiểu như thế std::function<void(int)>
, nội dung của nó sẽ không hiển thị đầy đủ và đối tượng hàm được tạo thủ công có thể nhanh hơn.
Mục tiêu của tiêu chuẩn hóa C ++ là lập trình cấp cao với chi phí bằng không đối với mã C thủ công.
Bây giờ bạn hiểu rằng f
thực tế của bạn là không trạng thái, sẽ có một câu hỏi khác trong đầu bạn: lambda không có trạng thái. Tại sao nó không có kích thước 0
?
Có câu trả lời ngắn gọn.
Tất cả các đối tượng trong C ++ phải có kích thước tối thiểu là 1 theo tiêu chuẩn và hai đối tượng cùng loại không thể có cùng địa chỉ. Chúng được kết nối với nhau, bởi vì một mảng kiểu T
sẽ có các phần tử được đặt sizeof(T)
cách nhau.
Bây giờ, vì nó không có trạng thái, đôi khi nó có thể chiếm không gian. Điều này không thể xảy ra khi nó ở "một mình", nhưng trong một số ngữ cảnh, nó có thể xảy ra. std::tuple
và mã thư viện tương tự khai thác thực tế này. Đây là cách nó làm việc:
Vì lambda tương đương với một lớp có operator()
quá tải, lambda không trạng thái (với một []
danh sách chụp) là tất cả các lớp trống. Họ có sizeof
của 1
. Trên thực tế, nếu bạn kế thừa từ chúng (được phép!), Chúng sẽ không chiếm dung lượng miễn là nó không gây ra xung đột địa chỉ cùng loại . (Đây được gọi là tối ưu hóa cơ sở trống).
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
những sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
là sizeof(int)
(tốt, ở trên là bất hợp pháp vì bạn không thể tạo ra một lambda trong một bối cảnh không đánh giá: bạn phải tạo một tên auto toy = make_toy(blah);
thì làm sizeof(blah)
, nhưng đó chỉ là tiếng ồn). sizeof([]{std::cout << "hello world!\n"; })
vẫn là 1
(trình độ tương tự).
Nếu chúng tôi tạo một loại đồ chơi khác:
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
này có hai bản sao của lambda. Vì họ không thể chia sẻ cùng một địa chỉ, sizeof(toy2(some_lambda))
là 2
!
struct
với anoperator()
)