Lambda yang dimaksud sebenarnya tidak memiliki status .
Memeriksa:
struct lambda {
auto operator()() const { return 17; }
};
Dan jika kita punya lambda f;
, itu adalah kelas kosong. Tidak hanya di atas secara lambda
fungsional mirip dengan lambda Anda, itu (pada dasarnya) bagaimana lambda Anda diimplementasikan! (Ini juga membutuhkan operator implisit cast to function pointer, dan namanya lambda
akan diganti dengan beberapa pseudo-guid yang dihasilkan kompiler)
Dalam C ++, objek bukanlah pointer. Itu adalah hal yang nyata. Mereka hanya menggunakan ruang yang dibutuhkan untuk menyimpan data di dalamnya. Penunjuk ke sebuah objek bisa lebih besar dari sebuah objek.
Meskipun Anda mungkin menganggap lambda itu sebagai penunjuk ke suatu fungsi, sebenarnya tidak. Anda tidak dapat menetapkan ulang auto f = [](){ return 17; };
ke fungsi atau lambda yang berbeda!
auto f = [](){ return 17; };
f = [](){ return -42; };
di atas adalah ilegal . Tidak ada ruang di f
toko mana fungsi akan disebut - bahwa informasi disimpan dalam jenis dari f
, tidak dalam nilai f
!
Jika Anda melakukan ini:
int(*f)() = [](){ return 17; };
atau ini:
std::function<int()> f = [](){ return 17; };
Anda tidak lagi menyimpan lambda secara langsung. Dalam kedua kasus ini, f = [](){ return -42; }
adalah legal - jadi dalam kasus ini, kami menyimpan fungsi mana yang kami panggil nilainya f
. Dan sizeof(f)
tidak lagi 1
, melainkan lebih sizeof(int(*)())
atau lebih besar (pada dasarnya, berukuran penunjuk atau lebih besar, seperti yang Anda harapkan. std::function
Memiliki ukuran min yang tersirat oleh standar (mereka harus dapat menyimpan callable "di dalam diri mereka sendiri" hingga ukuran tertentu) yang setidaknya sebesar fungsi pointer dalam praktiknya).
Dalam int(*f)()
kasus ini, Anda menyimpan penunjuk fungsi ke fungsi yang berperilaku seolah-olah Anda memanggil lambda itu. Ini hanya berfungsi untuk lambda tanpa negara (yang memiliki []
daftar tangkapan kosong ).
Dalam std::function<int()> f
kasus ini, Anda membuat std::function<int()>
instance kelas penghapusan tipe yang (dalam kasus ini) menggunakan penempatan baru untuk menyimpan salinan lambda size-1 dalam buffer internal (dan, jika lambda yang lebih besar diteruskan (dengan lebih banyak status ), akan menggunakan alokasi heap).
Sebagai tebakan, hal seperti ini mungkin yang menurut Anda sedang terjadi. Itu lambda adalah objek yang tipenya dijelaskan oleh tanda tangannya. Dalam C ++, diputuskan untuk membuat abstraksi biaya nol lambda atas implementasi objek fungsi manual. Ini memungkinkan Anda meneruskan lambda ke dalam std
algoritme (atau yang serupa) dan membuat kontennya terlihat sepenuhnya oleh kompiler saat membuat instance template algoritme. Jika lambda memiliki tipe seperti std::function<void(int)>
, isinya tidak akan terlihat sepenuhnya, dan objek fungsi buatan tangan mungkin lebih cepat.
Tujuan dari standardisasi C ++ adalah pemrograman tingkat tinggi dengan overhead nol pada kode C buatan tangan.
Sekarang setelah Anda memahami bahwa Anda f
sebenarnya tidak memiliki kewarganegaraan, seharusnya ada pertanyaan lain di kepala Anda: lambda tidak memiliki status. Mengapa tidak ada ukuran 0
?
Ada jawaban singkatnya.
Semua objek di C ++ harus memiliki ukuran minimal 1 di bawah standar, dan dua objek dengan jenis yang sama tidak boleh memiliki alamat yang sama. Ini terhubung, karena array tipe T
akan memiliki elemen yang ditempatkan sizeof(T)
terpisah.
Sekarang, karena tidak memiliki status, terkadang tidak memakan tempat. Hal ini tidak dapat terjadi jika "sendirian", tetapi dalam beberapa konteks dapat terjadi. std::tuple
dan kode pustaka serupa memanfaatkan fakta ini. Berikut cara kerjanya:
Karena lambda setara dengan kelas dengan operator()
beban berlebih, lambda tanpa status (dengan []
daftar tangkapan) adalah semua kelas kosong. Mereka memiliki sizeof
dari 1
. Faktanya, jika Anda mewarisi dari mereka (yang diperbolehkan!), Mereka tidak akan memakan tempat selama tidak menyebabkan benturan alamat tipe yang sama . (Ini dikenal sebagai pengoptimalan basis kosong).
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}; }
the sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
is sizeof(int)
(di atas adalah ilegal karena Anda tidak dapat membuat lambda dalam konteks yang tidak dievaluasi: Anda harus membuat nama auto toy = make_toy(blah);
lalu lakukan sizeof(blah)
, tetapi itu hanya noise). sizeof([]{std::cout << "hello world!\n"; })
masih 1
(kualifikasi serupa).
Jika kita membuat jenis mainan lain:
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}; }
ini memiliki dua salinan lambda. Karena mereka tidak dapat berbagi alamat yang sama, sizeof(toy2(some_lambda))
is 2
!
struct
denganoperator()
)