2012-12-09 13 views
7

la mia esperienza sembra che sia:C++ Indirizzo di oggetti lambda come parametri alle funzioni

  • un'espressione lambda creato all'interno di una chiamata di funzione viene distrutta subito dopo l'invocazione
  • Chiamata di una funzione che si aspetta un std::function crea un oggetto temporaneo (std :: funzione) dal lambda, e l'oggetto viene distrutto dopo l'invocazione

Questo comportamento può essere osservato con il seguente frammento di codice:

0.123,51641 milioni
const function<void()>* pointer; 

void a(const function<void()> & f) 
{ 
    pointer = &f; 
} 

void b() 
{ 
    (*pointer)(); 
} 

int main() 
{ 
    int value = 1; 
    std::cout << &value << std::endl; 

    // 1: this works  
    function<void()> f = [&]() { std::cout << &value << std::endl; }; 
    a(f); 

    // 2: this doesn't 
    a([&]() { std::cout << &value << std::endl; }); 

    /* modify the stack*/ 
    char data[1024]; 
    for (int i = 0; i < 1024; i++) 
     data[i] = i % 4; 

    b(); 

    return 0; 
} 

Che cosa sta effettivamente accadendo nel secondo caso? C'è un modo corretto per chiamare a() senza creare un oggetto esplicito std::function?

Edit:: Questo entrambe le versioni (1 e 2) compilare solo di destra, ma causa di variazioni uscite:

Versione 1:

0x7fffa70148c8 
0x7fffa70148c8 

Versione 2:

0x7fffa70148c8 
0 
+1

Cosa vuoi dire che il secondo caso "non funziona"? Compila? Crolla? Scrive "non funziona" sulla tua stampante? – jalf

+1

@jalf: Hai dimenticato: si ferma e prende fuoco. – Grizzly

+1

/* modifica lo stack */La maggior parte dei compilatori prealloca lo spazio necessario per ** tutte ** le variabili locali al momento dell'inserimento della funzione. –

risposta

2

Se si crea un elemento temporaneo, verrà rimosso alla fine della riga. Ciò significa che memorizzare un puntatore su di esso è una cattiva idea, come hai affermato correttamente.

Se si desidera memorizzare un puntatore su un std::function (o qualsiasi altra cosa veramente), è necessario assicurarsi che la durata della vita non termini prima di smettere di usare il puntatore. Ciò significa che hai davvero bisogno di un oggetto con nome di tipo std::function.

Per quanto riguarda ciò che accade nel secondo caso: si crea un lambda temporaneo da passare alla funzione. Poiché la funzione prevede un std::function, verrà creato un valore temporaneo std::function dal lambda. Entrambi saranno distrutti alla fine della linea. Quindi ora hai un puntatore a un temporaneo già distrutto, il che significa che provare a usare l'oggetto puntato ti porterà saldamente in un territorio di comportamento indefinito.

+0

, questo non è necessariamente vero per lambda in quanto vengono riscritti a funzioni "reali" in modo che rimangano nella memoria, allo stesso indirizzo . –

+0

Alla fine del contenente _full expression_ in realtà. In questo caso, è all'incirca quella linea. – sehe

+0

@BarnabasSzabolcs: ne sei sicuro? Un lambda può essere implicitamente convertito in un puntatore di funzione (quindi la funzione è da qualche parte, tanto è vero), ma il lambda in realtà verrà riscritto per essere identico a quel puntatore di funzione? sembra uno spreco in termini di opportunità di inlining mancato. Ad ogni modo, non importa, dal momento che il punto importante è che 'std :: function' è distrutto – Grizzly

1

Va bene per lambda stateless. I lambda stateless hanno una conversione implicita al tipo di puntatore a funzione.

Inoltre, vi è sempre una conversione implicita a std :: function <> indipendentemente dal tipo effettivo chiamabile.

Ci sono problemi con il mantenimento di un puntatore ai temporari, anche se. Non l'avevo notato in prima lettura del codice.

Questo non ha nulla a che fare con std :: function, ovviamente.

+0

È ok per memorizzare un puntatore ad una 'std :: function' temporanea, se l'oggetto racchiuso nella' funzione' è un functionpointer? – Grizzly

+0

No. I puntatori ai provvisori sono sempre cattivi. Sai, questo non ha nulla a che fare con std :: function <> veramente – sehe

+0

** I ** lo so, ma la tua risposta sembra implicare che lo sia (quindi potresti voler cambiare il testo;)) – Grizzly

0

Gli oggetti temporanei vengono distrutti alla fine dell'istruzione in cui sono stati creati. Lo hai appena dimostrato con un lambda.

La funzione std che si passa la prima volta non è temporanea, quindi dura quanto la variabile. In realtà memorizza una copia del temporaneo.

Per avere i tuoi lamd durano più di una linea, è necessario memorizzarli da qualche parte e avere una durata determinata.

Ci sono molti modi per farlo. La funzione Std è un metodo basato sulla cancellazione del tipo per eseguire la memorizzazione, ovvero memorizzare una funzione std anziché un puntatore a una funzione std. Modificare il tipo di pointer in modo che non sia puntatore (e non costante), eliminare lo & quando lo si assegna e chiamarlo direttamente.

In generale evitare di prendere e memorizzare l'indirizzo dei parametri della funzione di riferimento const come collegamento temporaneo ad essi, a meno che non si rilevi la possibilità di riferimento del rvalore in un altro sovraccarico.

+0

Un modo per memorizzare il lambda senza avere il sovraccarico di runtime della cancellazione del tipo fornita da std :: function? –

+0

@joaorafael In una funzione, l'auto deduce il tipo. In una classe template che avvolge la chiamata, un creatore basato su funzioni può dedurre il tipo lambda e passare alla classe in modo che la classe possa memorizzare un lambda non cancellato. Un approccio filosofico può chiederti se sai che questo è un collo di bottiglia per le prestazioni e se i tuoi sforzi non vengono spesi meglio altrove. Il codice che memorizza e utilizza il lambda deve essere una funzione del tipo lambdas se si desidera evitare l'overhead di cancellazione del tipo. – Yakk

Problemi correlati