2015-11-30 7 views
8

Ogni tanto mi venire con codice come questo:catturando tutto vs. catturare solo alcuni in una lambda

// lazy lambda: 
[&] { 
// use references to a, b, c 
} 

// pedantic lambda 
[&a, &b, &c] { 
// use references to a, b, c 
} 

Mi piacerebbe sapere quale delle lambda è migliore in termini di prestazioni e, forse, dimensione eseguibile secondo lo standard C++ 14 (o successivo) e la tua esperienza pratica con i compilatori.

+0

Se il lambda pigro finisce per catturare le stesse variabili del lambda pedante, non vedo alcun motivo per cui le prestazioni o anche i codici macchina generati dovrebbero differire. – alain

+2

IDK se consentito dallo standard o no, ma penserei che il compilatore catturerebbe solo le variabili che si usano. – NathanOliver

+3

La differenza è puramente sintattica, non semantica. Entrambi i lambda fanno la stessa cosa. –

risposta

3

Escludendo il caso banale in cui si acquisisce esplicitamente qualcosa che non è menzionato nel lambda, ci sono due casi d'angolo in cui potrebbe esserci una differenza.

In primo luogo, le acquisizioni implicite in genere non acquisiscono entità che non sono utilizzate odr (ma vedere l'elemento successivo per l'eccezione a questo). Questo include, tra le altre cose, le cose di cui operandi non valutate come quelli di decltype e sizeof, così come alcune variabili locali const e constexpr se usato in alcuni contesti (consultare [basic.def.odr] per l'intero insieme di regole):

void f(int){} 
void f2(const int &) {} 
void t() { 
    const int x = 8; 
    constexpr double y = 8; 
    const double z = 8; 
    auto g = []{ f(x); }; // OK 
    auto g1 = [=]{ f(x); }; // OK, does not capture x 

    // usually won't fire, though not guaranteed 
    static_assert(sizeof(g) == sizeof(g1), "!!"); 

    auto g2 = []{ f(y); }; // OK 
    auto g3 = []{ f(z); }; // Error, must capture z 

    auto g4 = []{ f2(x); }; // Error, must capture x since it's odr-used 
    auto g5 = [=]{ f2(x); }; // OK, captures x 
    auto g6 = []{ f2(+x); }; // OK, doesn't odr-use x 
    auto g7 = []{ f2(y); }; // OK 
} 

Se si scrive la lista di cattura manualmente, è possibile che catturerai più di quanto tecnicamente necessario, perché le regole che governano ciò che è o non è usato odr sono piuttosto complesse.

In secondo luogo, le acquisizioni implicite nei lambda generici cattureranno le cose utilizzate in un'espressione dipendente, anche quando non sono necessariamente odr-usate, per la sanità mentale. Prendendo in prestito an example from the standard:

void f(int, const int (&)[2] = {}) { } // #1 
void f(const int&, const int (&)[1]) { } // #2 
void test() { 
    const int x = 17; 
    auto g2 = [=](auto a) { 
    int selector[sizeof(a) == 1 ? 1 : 2]{}; 
    f(x, selector); // OK: is a dependent expression, so captures x 
        // Will actually odr-use x only if sizeof(a) == 1 
    }; 
} 

Tuttavia, non esiste alcun obbligo di catturare qualcosa che può-o-può-non-essere-ODR-utilizzato quando si scrive un lambda generica; ti viene richiesto di catturarlo solo se installi una specializzazione dell'operatore di chiamata di funzione che odr-usa la cosa. Pertanto, se non si chiama mai il lambda generico risultante in un modo che potrebbe utilizzare la cosa in questione, l'acquisizione implicita potrebbe catturare più del minimo necessario. Ad esempio, questo è consentito:

void test() { 
    const int x = 17; 
    auto g3 = [](auto a) { // no capture 
    int selector[sizeof(a) == 1 ? 1 : 2]{}; 
    f(x, selector); // OK for now 
    }; 
} 

finché non si chiama g3 con qualsiasi cosa la cui dimensione è 1: g3(0) è OK su sistemi tipici; g3('\0') no.

5

Non vi sono differenze in questo esempio. Il compilatore acquisirà solo le variabili che sono esplicitamente referenziate nel lambda, rendendo l'acquisizione lazy equivalente a quella esplicita.

Una cattura pigra è leggermente più comoda. Cambiare il corpo di lambda per usare variabili acquisite aggiuntive o rimuovere tutti i riferimenti a una variabile acquisita da una lambda esistente, normalmente richiederebbe anche l'aggiornamento di una lista di cattura esplicita. Con una cattura pigra il compilatore farà tutto per te.

1

Come detto sopra, non ci saranno differenze per quanto riguarda il codice generato. Tuttavia, credo, è meglio essere espliciti: offre una migliore leggibilità (più facile da ragionare sul ciclo di vita delle variabili) e offre anche un migliore supporto dal compilatore, producendo errori quando si accede alla variabile che non si voleva.

Credo che "cattura pigro" non sia affatto una buona caratteristica.

Problemi correlati