Bir lambda neden 1 bayt boyutuna sahiptir?


90

C ++ 'da bazı lambdaların hafızası üzerinde çalışıyorum, ancak boyutları beni biraz şaşırttı.

İşte test kodum:

#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;
}

Buradan çalıştırabilirsiniz: http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598

Çıktı:

17
0x7d90ba8f626f
1

Bu, lambda'mın boyutunun 1 olduğunu gösteriyor.

  • Bu nasıl mümkün olabilir?

  • Lambda, en azından, uygulanmasına bir işaretçi olmamalı mı?


17
işlev nesnesi olarak uygulandı (a structile operator())
george_ptr

14
Ve boş bir yapı boyutu 0 olamaz, dolayısıyla 1 sonuç. Bir şey yakalamayı deneyin ve boyutuna ne olduğunu görün.
Mohamad Elghawi

2
Neden bir lambda bir işaretçi olmalıdır ??? Çağrı operatörü olan bir nesnedir.
Kerrek SB

7
C ++ 'daki Lambdalar derleme zamanında bulunur ve çağrılar derleme veya bağlantı zamanında bağlanır (veya hatta satır içi). Bu nedenle , nesnenin kendisinde bir çalışma zamanı işaretçisine gerek yoktur . @KerrekSB Lambdaları uygulayan çoğu dil C ++ 'dan daha dinamik olduğundan, bir lambda'nın bir işlev işaretçisi içermesini beklemek doğal olmayan bir tahmin değildir.
Kyle Strand

2
@KerrekSB "önemli olan" - ne anlamda? Sebebi (tercihan bir işlev işaretçisi ihtiva eden daha az) bir kapama nesnesi boş olabilir olduğu için çağrılacak fonksiyon derleme / bağlantı zaman bilinmektedir. OP'nin yanlış anladığı şey bu. Yorumlarınızın işleri nasıl açıklığa kavuşturduğunu anlamıyorum.
Kyle Strand

Yanıtlar:


108

Söz konusu lambda aslında bir duruma sahip değildir .

Muayene etmek:

struct lambda {
  auto operator()() const { return 17; }
};

Ve olsaydı lambda f;, bu boş bir sınıftır. Yukarıdakiler sadece lambdalambda'nıza işlevsel olarak benzemekle kalmaz , aynı zamanda (temelde) lambda'nızın nasıl uygulandığıdır! (Ayrıca, işaretçi işleci işlevini yerine getirmek için örtük bir çevrim gerekir ve ad lambda, derleyici tarafından üretilen bazı sözde kılavuzlarla değiştirilecektir)

C ++ 'da nesneler işaretçi değildir. Gerçek şeyler bunlar. Yalnızca verileri içlerinde depolamak için gereken alanı kullanırlar. Bir nesneye işaretçi, bir nesneden daha büyük olabilir.

Lambda'yı bir işleve işaretçi olarak düşünebilirsiniz, ancak öyle değildir. Öğesini auto f = [](){ return 17; };farklı bir işleve veya lambda'ya yeniden atayamazsınız!

 auto f = [](){ return 17; };
 f = [](){ return -42; };

yukarıdaki yasa dışıdır . İçinde yer yoktur fmağazaya işlevi çağrılacak gidiyor - bilgi depolandığını tip ait olmayan değerinde, !ff

Bunu yaptıysanız:

int(*f)() = [](){ return 17; };

veya bu:

std::function<int()> f = [](){ return 17; };

artık lambdayı doğrudan depolamıyorsunuz. Her iki durumda f = [](){ return -42; }da yasaldır - yani bu durumlarda, hangi işlevi değerinde çağırdığımızı saklıyoruz f. Ve sizeof(f)artık değil 1, sizeof(int(*)())daha çok veya daha büyük (temelde, beklediğiniz gibi işaretçi boyutunda veya daha büyük olabilir. std::functionStandartta belirtilen minimum boyuta sahiptirler (belirli bir boyuta kadar "kendi içlerinde" çağrılabilirleri saklayabilmeleri gerekir) pratikte en az bir fonksiyon göstericisi kadar büyüktür).

Bu int(*f)()durumda, lambda çağırmışsınız gibi davranan bir işleve bir işlev işaretçisini depoluyorsunuz. Bu yalnızca durum bilgisi olmayan lambdalar ( []yakalama listesi boş olanlar) için işe yarar .

Bu std::function<int()> fdurumda, std::function<int()>(bu durumda) boyut-1 lambda'nın bir kopyasını dahili bir arabellekte depolamak için new yerleşimini kullanan bir tür silme sınıfı örneği oluşturuyorsunuz (ve daha büyük bir lambda geçirildiyse (daha fazla durumla) ), yığın ayırmayı kullanır).

Tahmin olarak, muhtemelen böyle bir şey olduğunu düşündüğünüz şeydir. Lambda, türü imzasıyla tanımlanan bir nesnedir. C ++ 'da, manuel fonksiyon nesnesi uygulaması üzerinden lambdas sıfır maliyetli soyutlamalar yapmaya karar verildi . Bu, bir lambda'yı bir stdalgoritmaya (veya benzerine) geçirmenize ve içeriğinin, algoritma şablonunu başlatırken derleyiciye tamamen görünür olmasını sağlar. Bir lambda benzeri bir türe sahip std::function<void(int)>olsaydı, içeriği tam olarak görünmezdi ve el yapımı bir işlev nesnesi daha hızlı olabilirdi.

C ++ standardizasyonunun amacı, el yapımı C kodu üzerinde sıfır ek yük ile yüksek seviyeli programlamadır.

Artık kendinizin faslında vatansız olduğunu anladığınıza göre, kafanızda başka bir soru daha olmalı: lambda'nın durumu yoktur. Neden boyutu yok 0?


Kısa cevap var.

C ++ 'daki tüm nesnelerin standardın altında minimum boyutu 1 olmalıdır ve aynı türden iki nesne aynı adrese sahip olamaz. Bunlar birbirine bağlıdır, çünkü bir dizi türü birbirinden ayrı Tyerleştirilmiş öğelere sahip olacaktır sizeof(T).

Şimdi, durumu olmadığı için bazen yer kaplamaz. Bu "yalnız" olduğunda gerçekleşemez, ancak bazı bağlamlarda olabilir. std::tupleve benzer kütüphane kodu bu gerçeği kullanır. Şu şekilde çalışır:

Lambda operator()aşırı yüklenmiş bir sınıfa eşdeğer olduğundan , durumsuz lambdalar ( []yakalama listesi olan) boş sınıflardır. Onlar sahip sizeofbir 1. Aslında, onlardan miras alırsanız (buna izin verilir!), Aynı türde bir adres çakışmasına neden olmadığı sürece yer kaplamazlar . (Bu, boş temel optimizasyonu olarak bilinir).

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)( yani , değerlendirilmemiş bir bağlamda bir lambda oluşturamayacağınız için yukarıdakiler yasa dışıdır: bir adlandırılmış auto toy = make_toy(blah);sonra do oluşturmanız gerekir sizeof(blah), ancak bu sadece gürültüdür). sizeof([]{std::cout << "hello world!\n"; })hala 1(benzer nitelikler).

Başka bir oyuncak türü yaratırsak:

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}; }

bu lambda'nın iki kopyasına sahiptir . Onlar aynı adresi paylaşamaz gibi, sizeof(toy2(some_lambda))olduğu 2!


6
Nit: Bir işlev işaretçisi bir boşluktan * daha küçük olabilir. İki tarihsel örnek: Öncelikle sizeof (void *) == sizeof (char *)> sizeof (struct *) == sizeof (int *) olan kelime adresli makineler. (void * ve char *, bir kelime içindeki ofseti tutmak için fazladan bitlere ihtiyaç duyar). İkincisi, void * / int * 'in segment + ofset olduğu ve tüm belleği kapsayabildiği, ancak işlevlerin tek bir 64K segment içine yerleştirildiği 8086 bellek modeli ( yani bir işlev göstericisi yalnızca 16 bittir).
Martin Bonner, Monica'yı

1
@martin doğru. Ekstra ()eklendi.
Yakk - Adam Nevraumont

50

Lambda bir işlev göstericisi değildir.

Bir lambda, bir sınıfın bir örneğidir. Kodunuz yaklaşık olarak şuna eşdeğerdir:

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;

Lambda'yı temsil eden iç sınıfın sınıf üyesi yoktur, dolayısıyla sizeof()1'dir ( başka bir yerde yeterince belirtilen nedenlerden dolayı 0 olamaz ).

Lambda'nız bazı değişkenleri yakalayacaksa, bunlar sınıf üyelerine eşdeğer sizeof()olacak ve buna göre belirteceksiniz.


3
"Başka bir yere" bağlantı verebilir misiniz, bu da neden sizeof()0 olamayacağını açıklıyor ?
user1717828

26

Derleyiciniz lambda'yı az çok aşağıdaki yapı türüne çevirir:

struct _SomeInternalName {
    int operator()() { return 17; }
};

int main()
{
     _SomeInternalName f;
     std::cout << f() << std::endl;
}

Bu yapının durağan olmayan üyeleri olmadığından, boş bir yapı ile aynı boyuta sahiptir 1.

Lambda'nıza boş olmayan bir yakalama listesi eklediğiniz anda bu değişir:

int i = 42;
auto f = [i]() { return i; };

Hangisi tercüme edilecek

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;
}

Oluşturulan yapının intyakalama için artık statik olmayan bir üye depolaması gerektiğinden , boyutu büyüyecektir sizeof(int). Siz daha fazla şey yakaladıkça boyut büyümeye devam edecektir.

(Lütfen yapısal benzetmeyi bir tuz tanesi ile alın. Lambda'ların dahili olarak nasıl çalıştığını anlamanın güzel bir yolu olsa da, bu derleyicinin ne yapacağının gerçek bir çevirisi değildir)


12

Lambda, mimumumda, uygulanmasına bir işaretçi olması gerekmez mi?

Şart değil. Standarda göre, benzersiz, adsız sınıfın boyutu uygulama tanımlıdır . [Expr.prim.lambda] , C ++ 14'ten alıntı (vurgu benim):

Lambda ifadesinin türü (aynı zamanda kapanış nesnesinin türüdür), özellikleri aşağıda açıklanan benzersiz, adsız bir birleşimsiz sınıf türüdür - kapanış türü adı verilir -.

[...]

Bir uygulama farklı aşağıda açıklanmıştır ne kapatma tipini tanımlayabilir Bu programın gözlemlenebilir davranışı değiştirmez sağlanan değiştirerek dışındaki :

- kapak tipinin boyutu ve / veya hizası ,

- kapatma türünün önemsiz şekilde kopyalanabilir olup olmadığı (Madde 9),

- kapanış tipinin standart yerleşim sınıfı (Madde 9) olup olmadığı veya

- kapatma türünün bir POD sınıfı olup olmadığı (Madde 9)

Sizin durumunuzda - kullandığınız derleyici için - 1 boyutu elde edersiniz, bu da düzeltildiği anlamına gelmez. Farklı derleyici uygulamaları arasında değişebilir.


Bu bitin geçerli olduğundan emin misin? Yakalama grubu olmayan bir lambda aslında bir "kapanış" değildir. (Standart boş yakalama grubu lambdalarına yine de "kapanmalar" olarak atıfta bulunuyor mu?)
Kyle Strand

1
Evet öyle. Bu, standardın söylediği şeydir: " Lambda ifadesinin değerlendirilmesi geçici bir prvalue ile sonuçlanır. Bu geçici, kapanış nesnesi olarak adlandırılır. ", Yakalama veya yakalama, bu bir kapanma nesnesi, sadece biri yukarı değerlerden yoksun olacaktır.
legends2k

Olumsuz oy vermedim, ancak muhtemelen olumsuz oy veren, bu cevabın değerli olduğunu düşünmüyor çünkü bu, neden bir çalışma zamanı işaretçisi eklemeden lambdaları uygulamanın mümkün olduğunu (teorik bir perspektiften, standartlar açısından değil) açıklamıyor . çağrı operatörü işlevi. (Sorunun altında KerrekSB ile yaptığım tartışmaya bakın.)
Kyle Strand

7

Gönderen http://en.cppreference.com/w/cpp/language/lambda :

Lambda ifadesi , en küçük blok kapsamı, sınıf kapsamı veya aşağıdakileri içeren ad alanı kapsamında bildirilen (ADL amaçları için) kapanış türü olarak bilinen benzersiz, adlandırılmamış birleşimsiz birleşik olmayan sınıf türünün adsız bir geçici prvalue nesnesi oluşturur. lambda ifadesi.

Lambda ifadesi kopyayla herhangi bir şeyi yakalarsa (ya örtük olarak yakalama cümlesiyle [=] ya da açıkça & karakterini içermeyen bir yakalamayla, örneğin [a, b, c]), kapanış türü adsız statik olmayan verileri içerir Bu şekilde ele geçirilen tüm varlıkların kopyalarını içeren, belirtilmemiş sırayla beyan edilen üyeler .

Referans ile yakalanan varlıklar için (varsayılan yakalama [&] ile veya &, örneğin [& a, & b, & c] karakterini kullanırken), kapatma tipinde ek veri üyelerinin beyan edilip edilmediği belirtilmemiştir.

Gönderen http://en.cppreference.com/w/cpp/language/sizeof

Boş bir sınıf türüne uygulandığında, her zaman 1 döndürür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.