Bu soru kodda tam olarak cevaplanamaz. Bir şekilde "eşdeğer" kod yazabilirsiniz, ancak standart bu şekilde belirtilmez.
Bu yoldan çekilişe girelim [expr.prim.lambda]
. Nota ilk şey kurucular sadece belirtilen olmasıdır [expr.prim.lambda.closure]/13
:
Lambda ifadesinde bir lambda yakalama ve varsayılan olarak varsayılan bir oluşturucu varsa lambda ifadesiyle ilişkilendirilen kapatma türü varsayılan bir yapıcıya sahip değildir . Varsayılan bir kopya oluşturucuya ve varsayılan bir taşıma yapıcısına ([class.copy.ctor]) sahiptir. Lambda ifadesi silinmiş bir kopya atama operatörüne sahiptir bir lambda yakalama ve varsayılan olarak kopyalanmış ve taşınmış vardır ([class.copy.assign]). [ Not: Bu özel üye işlevleri dolaylı olarak her zamanki gibi tanımlanır ve bu nedenle silindi olarak tanımlanabilir. - son not ]
Bu yüzden yarasadan hemen sonra, yapıcıların nesneleri yakalamanın resmi olarak tanımlanmadığı açık olmalıdır. Oldukça yakınlaşabilirsiniz (cppinsights.io cevabına bakın), ancak detaylar farklıdır (vaka 4 için bu cevaptaki kodun nasıl derlenmediğine dikkat edin).
Bunlar, durum 1'i tartışmak için gereken ana standart maddelerdir:
[expr.prim.lambda.capture]/10
[...]
Kopyala yakalanan her varlık için, kapatma türünde isimsiz statik olmayan bir veri üyesi bildirilir. Bu üyelerin beyan düzenleri açıklanmamıştır. Varlık bir nesneye başvuru ise, böyle bir veri üyesinin türü başvurulan tür, eğer varlık bir işleve başvuru ise başvurulan işlev türüne bir lvalue başvurusu veya aksi takdirde karşılık gelen yakalanan varlığın türü olur. Anonim birliğin bir üyesi kopya ile yakalanamaz.
[expr.prim.lambda.capture]/11
Lambda-ifadesinin bileşik ifadesindeki her id -ifadesiKopyala yakalanan bir varlığın tek kullanımlık olan , kapatma tipinin karşılık gelen adlandırılmamış veri üyesine erişime dönüştürülür. [...]
[expr.prim.lambda.capture]/15
Lambda ifadesi değerlendirildiğinde, kopya ile yakalanan varlıklar, sonuçta ortaya çıkan kapatma nesnesinin karşılık gelen statik olmayan her veri elemanını doğrudan başlatmak için kullanılır ve init yakalamalarına karşılık gelen statik olmayan veri elemanları şu şekilde başlatılır: ilgili başlatıcı ile gösterilir (kopya veya doğrudan başlatma olabilir). [...]
Bunu davanız 1'e uygulayalım:
Durum 1: değere göre yakalama / değere göre varsayılan yakalama
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Bu lambda'nın kapatma türünde isimsiz statik olmayan bir veri üyesi (diyelim ki __x
) int
( x
ne bir referans ne de bir işlev olduğu için ) türü olacaktır x
ve lambda gövdesi içindeki erişimler erişime dönüştürülür __x
. Lambda ifadesini değerlendirdiğimizde (yani atama yaparken lambda
), doğrudan __x
ilex
.
Kısacası, yalnızca bir kopya yer alır . Kapatma türünün kurucusu dahil değildir ve bunu "normal" C ++ ile ifade etmek mümkün değildir (kapama türünün bir toplu tür olmadığını unutmayın) ).
Referans yakalama şunları içerir [expr.prim.lambda.capture]/12
şunları :
Bir varlık referans ile yakalanır dolaylı veya açık bir şekilde yakalanmış ancak kopya tarafından yakalanmamışsa yakalanmıştır. Başvuru ile yakalanan varlıklar için ek adsız statik olmayan veri üyelerinin kapatma türünde bildirilip bildirilmeyeceği belirtilmez. [...]
Referansların yakalanmasıyla ilgili başka bir paragraf daha var, ancak bunu hiçbir yerde yapmıyoruz.
Yani, durum 2 için:
Durum 2: referans ile yakalama / referans ile varsayılan yakalama
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Bir üyenin kapatma türüne eklenip eklenmediğini bilmiyoruz. x
lambda gövdesinde doğrudanx
dışarıya . Bu, derleyicinin çözülmesine bağlıdır ve bunu, C ++ kodunun kaynak dönüşümü değil, bir ara ara dilde (derleyiciden derleyiciye farklılık gösterir) yapar.
Init yakalamaları [expr.prim.lambda.capture]/6
:
İnit-capture, formun bir değişkenini bildirir ve açıkça yakalar gibi davranır auto init-capture ;
, bildirim bölgesi lambda ifadesinin bileşik ifadesi olan , ancak aşağıdakiler hariç:
- (6.1) yakalama kopya ile yapılmışsa (aşağıya bakınız), yakalama ve değişken için bildirilen statik olmayan veri üyesi, statik olmayan verilerin ömrüne sahip aynı nesneye başvurmanın iki farklı yolu olarak ele alınır. ek kopya ve imha yapılmaz ve
- (6.2) yakalama referans olması durumunda, değişkenin ömrü, kapatma nesnesinin ömrü sona erdiğinde sona erer.
Bu göz önüne alındığında, 3. duruma bakalım:
Durum 3: Genelleştirilmiş init yakalama
auto lambda = [x = 33]() { std::cout << x << std::endl; };
Belirtildiği gibi, bunu auto x = 33;
kopya tarafından yaratılan ve açıkça yakalanan bir değişken olarak düşünün . Bu değişken, lambda gövdesi içinde sadece "görülebilir". Belirtildiği gibi[expr.prim.lambda.capture]/15
önce gibi, kapatma tipinin karşılık gelen elemanının ( __x
gelecek nesiller için) başlatılması, lambda ifadesinin değerlendirilmesi üzerine verilen başlatıcı tarafından yapılır.
Şüpheye yer bırakmamak için: Bu, her şeyin burada iki kez başlatıldığı anlamına gelmez. auto x = 33;
Bir "gibi" basit yakalar semantik miras ve tarif edilen başlatma bu anlam bir modifikasyonudur. Sadece bir başlatma gerçekleşir.
Bu ayrıca durum 4'ü de kapsar:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
Kapatma tipi eleman, __p = std::move(unique_ptr_var)
lambda ifadesi değerlendirildiğinde (yani l
, atandığında) başlatılır . Erişimlerde p
lambda gövdesinde yapılan erişim dönüşür __p
.
TL; DR: Yalnızca asgari sayıda kopya / başlatma / hamle yapılır (umulduğu / beklendiği gibi). Ben lambda'lar olduğunu varsaymak istiyorum değil tam (diğer sözdizimsel şeker aksine) bir kaynak dönüşüm açısından belirtilen çünkü kurucular açısından şeyleri ifade eden gereksiz işlemleri gerektirecek.
Umarım bu soruda ifade edilen korkuları çözer :)