C ++ 14'te Init Capture'larla C ++ Lambda Kod Üretimi


9

Anlamak / özellikle C ++ 14 eklenen genelleştirilmiş init yakalama lambdas yakalanan zaman üretilen kod kodunu açıklamaya çalışıyorum.

Aşağıda listelenen aşağıdaki kod örneklerini verin, derleyicinin ne üreteceğine dair şu anki bilgim

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

Şuna eşittir:

class __some_compiler_generated_name {
public:
    __some_compiler_generated_name(int x) : __x{x}{}
    void operator()() const { std::cout << __x << std::endl;}
private:
    int __x;
};

Dolayısıyla, biri yapıcı parametresine kopyalamak ve diğeri üyeye kopyalamak için vektör vb. Türler için pahalı olacak birden fazla kopya vardır.

Durum 2: referans ile yakalama / referans ile varsayılan yakalama

int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };

Şuna eşittir:

class __some_compiler_generated_name {
public:
    __some_compiler_generated_name(int& x) : x_{x}{}
    void operator()() const { std::cout << x << std::endl;}
private:
    int& x_;
};

Parametre bir referanstır ve üye bir referanstır, bu nedenle kopya olmaz. Vektör vb gibi türler için güzel.

Durum 3:

Genelleştirilmiş başlangıç ​​yakalama

auto lambda = [x = 33]() { std::cout << x << std::endl; };

Benim durumum, bu üyeye kopyalandığı anlamıyla Durum 1'e benzer.

Benim tahminime göre derleyici benzer kod üretir ...

class __some_compiler_generated_name {
public:
    __some_compiler_generated_name() : __x{33}{}
    void operator()() const { std::cout << __x << std::endl;}
private:
    int __x;
};

Ayrıca eğer aşağıdakileri varsa:

auto l = [p = std::move(unique_ptr_var)]() {
 // do something with unique_ptr_var
};

Yapıcı neye benzerdi? Ayrıca üyeye taşınıyor mu?


1
@ rafix07 Bu durumda oluşturulan analiz kodu derlenmeyecektir (bağımsız ptr üyesini bağımsız değişkenten kopyalamaya başlamaya çalışır). cppinsights genel bir öz almak için yararlıdır, ancak bu soruya açıkça cevap veremez.
Max Langhof

Derlemenin ilk adımı olarak lambda'nın functors'a çevirisi olduğunu düşünüyor musunuz, yoksa sadece eşdeğer kod mu arıyorsunuz (yani aynı davranış)? Belirli bir derleyicinin kod üretme biçimi (ve hangi kodu ürettiği) derleyiciye, sürüme, mimariye, bayraklara vb. Bağlıdır. Peki, belirli bir platform mu istiyorsunuz? Değilse, sorunuz gerçekten cevaplanamaz. Gerçekte oluşturulan kod dışında, listelediğiniz işlevlerden daha verimli olacaktır (örneğin, satır içi kurucular, gereksiz kopyalardan kaçınmak vb.).
Sander De Dycker

2
C ++ standardının bu konuda söyledikleriyle ilgileniyorsanız , [expr.prim.lambda] 'ya bakın . Burada bir cevap olarak özetlemek çok fazla.
Sander De Dycker

Yanıtlar:


2

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( xne bir referans ne de bir işlev olduğu için ) türü olacaktır xve 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. xlambda 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 ( __xgelecek 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 plambda 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 :)


9

Durum 1 [x](){} : Oluşturulan kurucu, constgereksiz kopyaları önlemek için argümanını büyük olasılıkla nitelikli referansla kabul edecektir :

__some_compiler_generated_name(const int& x) : x_{x}{}

Durum 2 [x&](){} : Buradaki varsayımlarınız doğru, xreferans olarak geçilmiş ve saklanmıştır.


Durum 3 [x = 33](){} : Yine doğru, xdeğere göre başlatılır.


Durum 4 [p = std::move(unique_ptr_var)] : Yapıcı şöyle görünecektir:

    __some_compiler_generated_name(std::unique_ptr<SomeType>&& x) :
        x_{std::move(x)}{}

yani evet, unique_ptr_varkapatılması "içine taşındı". Ayrıca bkz. Scott Meyer'ın Effective Modern C ++ ("Nesneleri kapanışa taşımak için init yakalamayı kullanma").


" const-kalifiye" Neden?
cpplearner

@cpplearner Mh, güzel soru. Sanırım bu zihinsel otomatizmlerden biri başladı çünkü ^ ^ En azından constbazı belirsizlik / daha iyi maç nedeniyle incitemez çünkü constvb. Neyse, sence kaldırmalıyım const?
lubgr

Bence const kalmalı, ne, eğer geçilen argüman gerçekten const ise?
Aconcagua

Yani burada iki hamle (veya kopya) yapısının gerçekleştiğini mi söylüyorsunuz?
Max Langhof

Maalesef, 4. davada (hamle için) ve 1. davada (kopyalar için) kastediyorum. Sorumun kopya kısmı ifadelerinize dayanıyor (ama bu ifadeleri sorguluyorum).
Max Langhof

5

Cppinsights.io kullanarak spekülasyon yapmaya daha az ihtiyaç vardır .

Durum 1:
Kod

#include <memory>

int main() {
    int x = 33;
    auto lambda = [x]() { std::cout << x << std::endl; };
}

Derleyici oluşturur

#include <iostream>

int main()
{
  int x = 6;

  class __lambda_5_16
  {
    int x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x).operator<<(std::endl);
    }

    // inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
    // inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
    public: __lambda_5_16(int _x)
    : x{_x}
    {}

  };

  __lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}

Durum 2:
Kod

#include <iostream>
#include <memory>

int main() {
    int x = 33;
    auto lambda = [&x]() { std::cout << x << std::endl; };
}

Derleyici oluşturur

#include <iostream>

int main()
{
  int x = 6;

  class __lambda_5_16
  {
    int & x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x).operator<<(std::endl);
    }

    // inline /*constexpr */ __lambda_5_16(const __lambda_5_16 &) = default;
    // inline /*constexpr */ __lambda_5_16(__lambda_5_16 &&) noexcept = default;
    public: __lambda_5_16(int & _x)
    : x{_x}
    {}

  };

  __lambda_5_16 lambda = __lambda_5_16(__lambda_5_16{x});
}

Durum 3:
Kod

#include <iostream>

int main() {
    auto lambda = [x = 33]() { std::cout << x << std::endl; };
}

Derleyici oluşturur

#include <iostream>

int main()
{

  class __lambda_4_16
  {
    int x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x).operator<<(std::endl);
    }

    // inline /*constexpr */ __lambda_4_16(const __lambda_4_16 &) = default;
    // inline /*constexpr */ __lambda_4_16(__lambda_4_16 &&) noexcept = default;
    public: __lambda_4_16(int _x)
    : x{_x}
    {}

  };

  __lambda_4_16 lambda = __lambda_4_16(__lambda_4_16{33});
}

Durum 4 (gayri resmi olarak):
Kod

#include <iostream>
#include <memory>

int main() {
    auto x = std::make_unique<int>(33);
    auto lambda = [x = std::move(x)]() { std::cout << *x << std::endl; };
}

Derleyici oluşturur

// EDITED output to minimize horizontal scrolling
#include <iostream>
#include <memory>

int main()
{
  std::unique_ptr<int, std::default_delete<int> > x = 
      std::unique_ptr<int, std::default_delete<int> >(std::make_unique<int>(33));

  class __lambda_6_16
  {
    std::unique_ptr<int, std::default_delete<int> > x;
    public: 
    inline void operator()() const
    {
      std::cout.operator<<(x.operator*()).operator<<(std::endl);
    }

    // inline __lambda_6_16(const __lambda_6_16 &) = delete;
    // inline __lambda_6_16(__lambda_6_16 &&) noexcept = default;
    public: __lambda_6_16(std::unique_ptr<int, std::default_delete<int> > _x)
    : x{_x}
    {}

  };

  __lambda_6_16 lambda = __lambda_6_16(__lambda_6_16{std::unique_ptr<int, 
                                                     std::default_delete<int> >
                                                         (std::move(x))});
}

Ve bu son kod parçasının sorunuza cevap verdiğine inanıyorum. Bir hareket oluşur, ancak yapıcıda [teknik olarak] gerçekleşmez.

Yakalamaların kendisi değildir const, ancak operator()işlevin olduğunu görebilirsiniz. Doğal olarak, yakalamaları değiştirmeniz gerekirse lambda'yı işaretlersiniz mutable.


Son durum için gösterdiğiniz kod derlenmiyor. "Bir hareket oluşur, ancak yapıcıda [teknik olarak] değil" sonucu bu kod tarafından desteklenemez.
Max Langhof

Kod durumda 4'ün kesinlikle benim Mac derliyor. Bunu şaşırdım cppinsights üretilen genişletilmiş kod derleme yapmaz. Site, bu noktada benim için oldukça güvenilir oldu. Onlarla bir sorun oluşturacağım. EDIT: Ben oluşturulan kod derleme olmadığını onaylamak; bu düzenleme olmadan açık değildi.
14'te sweenish

1
İlgilenmeniz durumunda konuyla ilgili bağlantı: github.com/andreasfertig/cppinsights/issues/258 Yine de siteyi SFINAE'yi test etmek ve örtük yayınların oluşup oluşmayacağı konusunda tavsiye ederim.
sweenish
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.