Saya bekerja dengan memori beberapa lambda di C ++, tapi saya agak bingung dengan ukurannya.
Ini kode tes saya:
#include <iostream>
#include <string>
int main()
{
auto f = [](){ return 17; };
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;
}
Anda dapat menjalankannya di sini: http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598
Ouptutnya adalah:
17
0x7d90ba8f626f
1
Ini menunjukkan bahwa ukuran lambda saya adalah 1.
Bagaimana ini mungkin?
Bukankah sebaiknya lambda menjadi, setidaknya, penunjuk untuk implementasinya?
struct
denganoperator()
)Jawaban:
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 secaralambda
fungsional mirip dengan lambda Anda, itu (pada dasarnya) bagaimana lambda Anda diimplementasikan! (Ini juga membutuhkan operator implisit cast to function pointer, dan namanyalambda
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 darif
, tidak dalam nilaif
!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 nilainyaf
. Dansizeof(f)
tidak lagi1
, melainkan lebihsizeof(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 membuatstd::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 sepertistd::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 ukuran0
?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 ditempatkansizeof(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 memilikisizeof
dari1
. 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"; } ))
issizeof(int)
(di atas adalah ilegal karena Anda tidak dapat membuat lambda dalam konteks yang tidak dievaluasi: Anda harus membuat namaauto toy = make_toy(blah);
lalu lakukansizeof(blah)
, tetapi itu hanya noise).sizeof([]{std::cout << "hello world!\n"; })
masih1
(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))
is2
!sumber
()
ditambahkan.Lambda bukanlah penunjuk fungsi.
Lambda adalah turunan dari kelas. Kode Anda kira-kira sama dengan:
class f_lambda { public: auto operator() { return 17; } }; f_lambda f; std::cout << f() << std::endl; std::cout << &f << std::endl; std::cout << sizeof(f) << std::endl;
Kelas internal yang mewakili lambda tidak memiliki anggota kelas, oleh karena itu kelasnya
sizeof()
adalah 1 (tidak boleh 0, karena alasan yang dinyatakan secara memadai di tempat lain ).Jika lambda Anda menangkap beberapa variabel, mereka akan setara dengan anggota kelas, dan Anda
sizeof()
akan menunjukkannya.sumber
sizeof()
tidak boleh 0?Kompiler Anda kurang lebih menerjemahkan lambda ke tipe struct berikut:
struct _SomeInternalName { int operator()() { return 17; } }; int main() { _SomeInternalName f; std::cout << f() << std::endl; }
Karena struct tersebut tidak memiliki anggota non-statis, ia memiliki ukuran yang sama dengan struct kosong, yaitu
1
.Itu berubah segera setelah Anda menambahkan daftar tangkapan yang tidak kosong ke lambda Anda:
int i = 42; auto f = [i]() { return i; };
Yang akan diterjemahkan ke
struct _SomeInternalName { int i; _SomeInternalName(int outer_i) : i(outer_i) {} int operator()() { return i; } }; int main() { int i = 42; _SomeInternalName f(i); std::cout << f() << std::endl; }
Karena struct yang dihasilkan sekarang perlu menyimpan anggota non-statis
int
untuk penangkapan, ukurannya akan bertambahsizeof(int)
. Ukurannya akan terus bertambah saat Anda menangkap lebih banyak barang.(Silakan ambil analogi struct dengan sebutir garam. Meskipun ini adalah cara yang bagus untuk menjelaskan tentang bagaimana lambda bekerja secara internal, ini bukan terjemahan literal dari apa yang akan dilakukan kompilator)
sumber
Belum tentu. Menurut standar, ukuran kelas yang unik dan tidak bernama ditentukan oleh implementasi . Kutipan dari [expr.prim.lambda] , C ++ 14 (penekanan saya):
Dalam kasus Anda - untuk kompiler yang Anda gunakan - Anda mendapatkan ukuran 1, yang tidak berarti itu sudah diperbaiki. Ini dapat bervariasi antara implementasi compiler yang berbeda.
sumber
Dari http://en.cppreference.com/w/cpp/language/lambda :
Dari http://en.cppreference.com/w/cpp/language/sizeof
sumber