Perché non riesco a catturare questo per riferimento ('& this') in lambda?


91

Capisco che il modo corretto per acquisire this(modificare le proprietà degli oggetti) in un lambda è il seguente:

auto f = [this] () { /* ... */ };

Ma sono curioso della seguente particolarità che ho visto:

class C {
    public:
        void foo() {
            // auto f = [] () { // this not captured
            auto f = [&] () { // why does this work?
            // auto f = [&this] () { // Expected ',' before 'this'
            // auto f = [this] () { // works as expected
                x = 5;
            };
            f();
        }

    private:
        int x;
};

La stranezza da cui sono confuso (e vorrei una risposta) è il motivo per cui funziona:

auto f = [&] () { /* ... */ }; // capture everything by reference

E perché non posso acquisire in modo esplicito thisper riferimento:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.

6
Perché dovresti volerlo ? In termini di cose per le quali un riferimento a un puntatore potrebbe mai essere utile: thisnon può essere modificato, non è abbastanza grande per fare un riferimento più velocemente ... e comunque , in realtà non esiste , quindi ha nessuna vita reale, il che significa che qualsiasi riferimento ad essa sarebbe sospeso per definizione. thisè un prvalue, non un lvalue.
underscore_d

Risposte:


111

Il motivo [&this]non funziona è perché si tratta di un errore di sintassi. Ogni parametro separato da virgole in lambda-introducerè un capture:

capture:
    identifier
    & identifier
    this

Puoi vedere che &thisnon è consentito sintatticamente. Il motivo per cui non è consentito è perché non vorresti mai acquisire thisper riferimento, poiché si tratta di un piccolo puntatore const. Vorresti solo passarlo per valore, quindi la lingua non supporta l'acquisizione thisper riferimento.

Per catturare in modo thisesplicito puoi usare [this]come file lambda-introducer.

Il primo capturepuò essere un capture-defaultche è:

capture-default:
    &
    =

Ciò significa catturare automaticamente tutto ciò che uso, rispettivamente per riferimento ( &) o per valore ( =) - tuttavia il trattamento di thisè speciale - in entrambi i casi viene catturato per valore per le ragioni fornite in precedenza (anche con un'acquisizione predefinita di &, che di solito significa cattura per riferimento).

5.1.2.7/8:

Ai fini della ricerca del nome (3.4), determinando il tipo e il valore di this(9.3.2) e trasformando espressioni id che si riferiscono a membri di classi non statiche in espressioni di accesso ai membri di classe usando (*this)(9.3.1), l'istruzione composta [OF THE LAMBDA] è considerato nel contesto dell'espressione lambda.

Quindi il lambda si comporta come se fosse parte della funzione membro che lo racchiude quando si usano i nomi dei membri (come nel tuo esempio l'uso del nome x), quindi genererà "usi impliciti" thisproprio come fa una funzione membro.

Se un'acquisizione lambda include un'acquisizione predefinita &, gli identificatori nell'acquisizione lambda non devono essere preceduti da &. Se un'acquisizione lambda include un'acquisizione predefinita, ovvero =l'acquisizione lambda non deve contenere this e ogni identificatore che contiene deve essere preceduto da &. Un identificatore o thisnon deve apparire più di una volta in una cattura lambda.

Così si può utilizzare [this], [&],[=] o [&,this]come lambda-introducerper catturare il thispuntatore per valore.

Tuttavia [&this]e [=, this]sono mal formati. In quest'ultimo caso gcc avverte forgivingly per [=,this]che explicit by-copy capture of ‘this’ redundant with by-copy capture default, piuttosto che errori.


3
@KonradRudolph: E se volessi catturare alcune cose per valore e altre per riferimento? O vuoi essere molto esplicito con ciò che catturi?
Xeo

2
@KonradRudolph: è una funzione di sicurezza. Potresti catturare accidentalmente nomi che non intendi.
Andrew Tomazos

8
@KonradRudolph: i costrutti a livello di blocco non copiano magicamente un puntatore agli oggetti che utilizzano in un nuovo tipo anonimo invisibile che può quindi sopravvivere all'ambito di inclusione, semplicemente utilizzando il nome degli oggetti in un'espressione. La cattura lambda è molto più pericolosa.
Andrew Tomazos

5
@KonradRudolph direi "usa [&]se stai facendo qualcosa come creare un blocco da passare in una struttura di controllo", ma cattura esplicitamente se stai producendo un lambda che verrà utilizzato per scopi meno semplici. [&]è un'idea orribile se il lambda sopravviverà allo scopo attuale. Tuttavia, molti usi di lambda sono solo modi per passare blocchi alle strutture di controllo e il blocco non sopravviverà al blocco in cui è stato creato nell'ambito.
Yakk - Adam Nevraumont

2
@Ruslan: No, thisè una parola chiave, thisnon è un identificatore.
Andrew Tomazos

6

Perché lo standard non ha &thisnegli elenchi di Catture:

N4713 8.4.5.2 Catture:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer
  1. Ai fini dell'acquisizione lambda, un'espressione fa potenzialmente riferimento a entità locali come segue:

    7.3 Questa espressione fa potenzialmente riferimento * a questo.

Quindi, lo standard garantisce thised *thisè valido e &thisnon è valido. Inoltre, catturare thissignifica catturare *this(che è un lvalue, l'oggetto stesso) per riferimento , piuttosto che catturare il thispuntatore per valore !


*thisacquisisce l'oggetto in base al valore
sp2danny
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.