2016-07-13 30 views
24

Ho una funzione in una classe che definisce un lambda e lo memorizza in una variabile statica locale:Quando è "questo" catturato in una lambda?

class A 
{ 
public: 
    void call_print() 
    { 
     static auto const print_func = [this] { 
      print(); 
     }; 

     print_func(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

Ho anche eseguire il seguente test:

int main() 
{ 
    A a; 
    B b; 

    a.call_print(); 
    b.call_print(); 
} 

(Live Sample)

Quello che mi aspetto di stampare è:

A::print() 
B::print() 

Ma quello che proprio mi piace è:

A::print() 
A::print() 

(indirizzo stesso oggetto viene stampato anche con ciascuno)

Ho il sospetto che questo è dovuto alla cattura this. Supponevo che avrebbe catturato il valore di this quando è stato chiamato, tuttavia sembra essere catturato nel momento in cui viene definita la lambda.

Qualcuno potrebbe spiegare la semantica delle catture lambda? Quando vengono effettivamente forniti alla funzione? È lo stesso per tutti i tipi di cattura, oppure è this un caso speciale? La rimozione di static risolve il problema, tuttavia nel mio codice di produzione sto effettivamente memorizzando il lambda in un oggetto leggermente più pesante che rappresenta uno slot a cui inserisco successivamente un segnale.

risposta

36

Questo non ha nulla a che fare con la semantica della cattura lambda. È semplicemente come funziona static.

Una variabile con ambito funzionale static è inizializzata esattamente una volta. C'è sempre solo un tale oggetto nel tuo intero programma. Sarà inizializzato la prima volta che viene chiamata la funzione (più precisamente, la prima volta che viene eseguita l'istruzione static). Pertanto, l'espressione utilizzata per inizializzare la variabile static viene invocata solo una volta.

Quindi, se una variabile con ambito funzionale static è inizializzata con dati basati su uno dei parametri della funzione (come this), otterrà solo i parametri dalla prima chiamata di quella funzione.

Il codice crea una singola lambda. Non crea diversi lambda per ogni invocazione della funzione.

Il comportamento che si desidera non è una variabile locale locale static, ma un membro oggetto. Quindi basta mettere un oggetto std::function nella classe stessa e avere call_print inizializzarlo se è vuoto.

+11

Potrebbe valere la pena di aggiungere che questo rappresenta in realtà uno stile di programmazione molto pericoloso, dal momento che se 'a' viene cancellato, le future chiamate a' call_print' invocheranno un comportamento indefinito e molto probabilmente un crash. – Xirema

+0

@Xirema Questo è vero, ma non penso che questo sia il comportamento che sta cercando di ottenere. –

+0

Capisco la semantica di 'static', quello che sto cercando di capire è se il lambda" promette "di catturare' this' * tardi *, o se è catturato * giusto allora *. Hai affrontato questo nella tua risposta, però. Volevo che il contenitore per il lambda fosse statico, ma non le catture. Speravo che fossero separati tra definizione e invocazione. –

2

In effetti, il valore della cattura viene impostato quando viene definita la lambda, non quando viene chiamata. Poiché stai impostando una variabile statica sull'espressione che definisce il lambda, ciò avviene solo la prima volta che viene chiamata la funzione call_print (dalle regole che governano le variabili statiche). Pertanto tutte le chiamate call_print in seguito ottengono lo stesso lambda, quello il cui this è impostato su &a.

12

Il lambda viene istanziato la prima volta che viene richiamata la funzione di chiusura A::call_print(). Dalla prima volta che lo chiamate su un oggetto A, viene catturato lo this di quell'oggetto.Se invertito l'ordine di invocazione, si vedrebbe un risultato diverso:

b.call_print(); 
a.call_print(); 

uscita:

B::print() 
B::print() 

E più a che fare con la semantica di inizializzazione degli oggetti statici funzione locale rispetto a quelli di cattura lambda.

8

Le variabili locali statiche vengono inizializzate la prima volta che viene eseguita la dichiarazione.

In questo caso, è:

  1. Creare una lambda, catturando & A come questo, che chiama A.Print();
  2. assegnare tale lambda a print_func
  3. chiamata che lambda (attraverso print_func)
  4. chiamata che lambda di nuovo.

I valori catturati vengono sempre catturati quando viene creata la lambda, che in questo caso è durante la prima chiamata a call_print.

4

Non penso che la cattura sia il problema qui, ma la parola chiave static. Pensate al vostro codice come questo:

class A 
{ 
public: 
    void call_print() 
    { 
     static A* ptr = this; 

     ptr->print(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

Questo è più o meno lo stesso senza un lambda.

Se si guarda il codice diventa abbastanza chiaro che la chiamata a.call_print(); inizializza ptr con un puntatore all'oggetto a che viene poi utilizzato più avanti.

3

Questa domanda non riguarda tanto il comportamento di lambda, quanto il comportamento delle variabili statiche nelle funzioni.

La variabile viene creata con il primo time code che scorre su di esso, utilizzando qualsiasi variabile disponibile in quel momento.

esempio:

#include <iostream> 

int foo(int v) 
{ 
    static int value = v; 
    return v; 
}; 

int main() 
{ 
    std::cout << foo(10) << std::endl; 
    std::cout << foo(11) << std::endl; 
} 

previsto:

10 
10 

Perché è equivalente a:

foo::value = 10; 
std::cout << foo::value << std::endl; 
// foo::value = 11; (does not happen) 
std::cout << foo::value << std::endl;