Sposta l’acquisizione in lambda

Come faccio a catturare per mossa (noto anche come riferimento rvalue) in un lambda C ++ 11?

Sto cercando di scrivere qualcosa del genere:

std::unique_ptr myPointer(new int); std::function = [std::move(myPointer)]{ (*myPointer) = 4; }; 

Cattura lambda generalizzata in C ++ 14

In C ++ 14 avremo la cosiddetta cattura generalizzata lambda . Ciò consente di spostare la cattura. Quanto segue sarà il codice legale in C ++ 14:

 using namespace std; // a unique_ptr is move-only auto u = make_unique( some, parameters ); // move the unique_ptr into the lambda go.run( [ u{move(u)} ] { do_something_with( u ); } ); 

Ma è molto più generale nel senso che le variabili catturate possono essere inizializzate con qualcosa di simile:

 auto lambda = [value = 0] mutable { return ++value; }; 

In C ++ 11 questo non è ancora ansible, ma con alcuni trucchi che coinvolgono tipi di helper. Fortunatamente, il compilatore Clang 3.4 implementa già questa fantastica funzionalità. Il compilatore verrà rilasciato a dicembre 2013 o gennaio 2014, se verrà mantenuto il ritmo di rilascio recente .

AGGIORNAMENTO: Il compilatore Clang 3.4 è stato rilasciato il 6 gennaio 2014 con la suddetta funzione.

Una soluzione alternativa per spostare l’acquisizione

Ecco un’implementazione di una funzione di aiuto make_rref che aiuta l’acquisizione di movimenti artificiali

 #include  #include  #include  template  struct rref_impl { rref_impl() = delete; rref_impl( T && x ) : x{std::move(x)} {} rref_impl( rref_impl & other ) : x{std::move(other.x)}, isCopied{true} { assert( other.isCopied == false ); } rref_impl( rref_impl && other ) : x{std::move(other.x)}, isCopied{std::move(other.isCopied)} { } rref_impl & operator=( rref_impl other ) = delete; T && move() { return std::move(x); } private: T x; bool isCopied = false; }; template rref_impl make_rref( T && x ) { return rref_impl{ std::move(x) }; } 

Ed ecco un caso di test per quella funzione che è andata a buon fine sul mio gcc 4.7.3.

 int main() { std::unique_ptr p{new int(0)}; auto rref = make_rref( std::move(p) ); auto lambda = [rref]() mutable -> std::unique_ptr { return rref.move(); }; assert( lambda() ); assert( !lambda() ); } 

Lo svantaggio qui è che lambda è copiabile e quando viene copiata l’asserzione nel costruttore di rref_impl di rref_impl fallisce portando a un bug di runtime. La seguente potrebbe essere una soluzione migliore e ancora più generica perché il compilatore catturerà l’errore.

Emulazione di cattura lambda generalizzata in C ++ 11

Ecco un’altra idea, su come implementare la cattura lambda generalizzata. L’uso della funzione capture() (la cui implementazione è trovata più in basso) è la seguente:

 #include  #include  int main() { std::unique_ptr p{new int(0)}; auto lambda = capture( std::move(p), []( std::unique_ptr & p ) { return std::move(p); } ); assert( lambda() ); assert( !lambda() ); } 

Qui lambda è un object funtore (quasi un vero lambda) che ha catturato std::move(p) quando viene passato a capture() . Il secondo argomento di capture è un lambda che prende la variabile acquisita come argomento. Quando lambda viene usato come object funzione, tutti gli argomenti che gli vengono passati verranno inoltrati al lambda interno come argomenti dopo la variabile catturata. (Nel nostro caso non ci sono ulteriori argomenti da inoltrare). In sostanza, accade lo stesso della soluzione precedente. Ecco come viene implementata la capture :

 #include  template  class capture_impl { T x; F f; public: capture_impl( T && x, F && f ) : x{std::forward(x)}, f{std::forward(f)} {} template  auto operator()( Ts&&...args ) -> decltype(f( x, std::forward(args)... )) { return f( x, std::forward(args)... ); } template  auto operator()( Ts&&...args ) const -> decltype(f( x, std::forward(args)... )) { return f( x, std::forward(args)... ); } }; template  capture_impl capture( T && x, F && f ) { return capture_impl( std::forward(x), std::forward(f) ); } 

Questa seconda soluzione è anche più pulita, in quanto disabilita la copia della lambda, se il tipo catturato non è copiabile. Nella prima soluzione che può essere verificata solo in fase di esecuzione con un assert() .

Puoi anche usare std::bind per catturare unique_ptr :

 std::function f = std::bind( [] (std::unique_ptr& p) { *p=4; }, std::move(myPointer) ); 

Puoi ottenere la maggior parte di ciò che vuoi usando std::bind , come questo:

 std::unique_ptr myPointer(new int{42}); auto lambda = std::bind([](std::unique_ptr& myPointerArg){ *myPointerArg = 4; myPointerArg.reset(new int{237}); }, std::move(myPointer)); 

Il trucco qui è che invece di catturare il tuo object di solo spostamento nell’elenco di catture, lo facciamo diventare un argomento e quindi usiamo l’applicazione parziale tramite std::bind per farlo sparire. Nota che il lambda lo prende come riferimento , perché in realtà è memorizzato nell’object bind. Ho anche aggiunto il codice che scrive sull’object mobile attuale, perché è qualcosa che potresti voler fare.

In C ++ 14, è ansible utilizzare la cattura lambda generalizzata per raggiungere gli stessi fini, con questo codice:

 std::unique_ptr myPointer(new int{42}); auto lambda = [myPointerCapture = std::move(myPointer)]() mutable { *myPointerCapture = 56; myPointerCapture.reset(new int{237}); }; 

Ma questo codice non ti compra nulla che non avessi in C ++ 11 tramite std::bind . (Ci sono alcune situazioni in cui la cattura lambda generalizzata è più potente, ma non in questo caso).

Ora c’è solo un problema; hai voluto inserire questa funzione in una funzione std::function , ma quella class richiede che la funzione sia CopyConstructible , ma non lo è, è solo MoveConstructible perché memorizza uno std::unique_ptr che non è CopyConstructible .

Per risolvere il problema con la class wrapper e un altro livello di riferimento indiretto, ma forse non hai affatto bisogno di std::function . A seconda delle tue esigenze, potresti essere in grado di usare std::packaged_task ; farebbe lo stesso lavoro di std::function , ma non richiede che la funzione sia copiabile, solo mobile (analogamente, std::packaged_task è solo mobile). Il rovescio della medaglia è che poiché è destinato ad essere usato insieme a std :: future, puoi chiamarlo solo una volta.

Ecco un breve programma che mostra tutti questi concetti.

 #include  // for std::bind #include  // for std::unique_ptr #include  // for std::move #include  // for std::packaged_task #include  // printing #include  // for std::result_of #include  void showPtr(const char* name, const std::unique_ptr& ptr) { std::cout << "- &" << name << " = " << &ptr << ", " << name << ".get() = " << ptr.get(); if (ptr) std::cout << ", *" << name << " = " << *ptr; std::cout << std::endl; } // If you must use std::function, but your function is MoveConstructable // but not CopyConstructable, you can wrap it in a shared pointer. template  class shared_function : public std::shared_ptr { public: using std::shared_ptr::shared_ptr; template  auto operator()(Args&&...args) const -> typename std::result_of::type { return (*(this->get()))(std::forward(args)...); } }; template  shared_function make_shared_fn(F&& f) { return shared_function{ new typename std::remove_reference::type{std::forward(f)}}; } int main() { std::unique_ptr myPointer(new size_t{42}); showPtr("myPointer", myPointer); std::cout << "Creating lambda\n"; #if __cplusplus == 201103L // C++ 11 // Use std::bind auto lambda = std::bind([](std::unique_ptr& myPointerArg){ showPtr("myPointerArg", myPointerArg); *myPointerArg *= 56; // Reads our movable thing showPtr("myPointerArg", myPointerArg); myPointerArg.reset(new size_t{*myPointerArg * 237}); // Writes it showPtr("myPointerArg", myPointerArg); }, std::move(myPointer)); #elif __cplusplus > 201103L // C++14 // Use generalized capture auto lambda = [myPointerCapture = std::move(myPointer)]() mutable { showPtr("myPointerCapture", myPointerCapture); *myPointerCapture *= 56; showPtr("myPointerCapture", myPointerCapture); myPointerCapture.reset(new size_t{*myPointerCapture * 237}); showPtr("myPointerCapture", myPointerCapture); }; #else #error We need C++11 #endif showPtr("myPointer", myPointer); std::cout << "#1: lambda()\n"; lambda(); std::cout << "#2: lambda()\n"; lambda(); std::cout << "#3: lambda()\n"; lambda(); #if ONLY_NEED_TO_CALL_ONCE // In some situations, std::packaged_task is an alternative to // std::function, eg, if you only plan to call it once. Otherwise // you need to write your own wrapper to handle move-only function. std::cout << "Moving to std::packaged_task\n"; std::packaged_task f{std::move(lambda)}; std::cout << "#4: f()\n"; f(); #else // Otherwise, we need to turn our move-only function into one that can // be copied freely. There is no guarantee that it'll only be copied // once, so we resort to using a shared pointer. std::cout << "Moving to std::function\n"; std::function f{make_shared_fn(std::move(lambda))}; std::cout << "#4: f()\n"; f(); std::cout << "#5: f()\n"; f(); std::cout << "#6: f()\n"; f(); #endif } 

Ho messo un programma sopra a Coliru , così puoi correre e giocare con il codice.

Ecco alcuni risultati tipici ...

 - &myPointer = 0xbfffe5c0, myPointer.get() = 0x7ae3cfd0, *myPointer = 42 Creating lambda - &myPointer = 0xbfffe5c0, myPointer.get() = 0x0 #1: lambda() - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 42 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 2352 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424 #2: lambda() - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 31215744 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032 #3: lambda() - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 1978493952 - &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360 Moving to std::function #4: f() - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608 #5: f() - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2967666688 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808 #6: f() - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 2022178816 - &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2515009536 

Si arriva a vedere le posizioni dell'heap che vengono riutilizzate, mostrando che std::unique_ptr funziona correttamente. Vedete anche la funzione stessa muoversi quando la riponiamo in un wrapper che alimentiamo a std::function .

Se passiamo a usare std::packaged_task , diventerà l'ultima parte

 Moving to std::packaged_task #4: f() - &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360 - &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496 - &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608 

quindi vediamo che la funzione è stata spostata, ma anziché spostarsi sull'heap, è all'interno di std::packaged_task che è in pila.

Spero che questo ti aiuti!

Stavo guardando queste risposte, ma ho trovato difficile bind e capire. Quindi quello che ho fatto è stato creare una class che è stata spostata in copia. In questo modo, è esplicito con ciò che sta facendo.

 #include  #include  #include  #include  #include  namespace detail { enum selection_enabler { enabled }; } #define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), ::detail::selection_enabler> \ = ::detail::enabled // This allows forwarding an object using the copy constructor template  struct move_with_copy_ctor { // forwarding constructor template , move_with_copy_ctor>::value ) > move_with_copy_ctor(T2&& object) : wrapped_object(std::forward(object)) { } // move object to wrapped_object move_with_copy_ctor(T&& object) : wrapped_object(std::move(object)) { } // Copy constructor being used as move constructor. move_with_copy_ctor(move_with_copy_ctor const& object) { std::swap(wrapped_object, const_cast(object).wrapped_object); } // access to wrapped object T& operator()() { return wrapped_object; } private: T wrapped_object; }; template  move_with_copy_ctor make_movable(T&& object) { return{ std::forward(object) }; } auto fn1() { std::unique_ptr> x(new int(1) , [](int * x) { std::cout << "Destroying " << x << std::endl; delete x; }); return [y = make_movable(std::move(x))]() mutable { std::cout << "value: " << *y() << std::endl; return; }; } int main() { { auto x = fn1(); x(); std::cout << "object still not deleted\n"; x(); } std::cout << "object was deleted\n"; } 

La class move_with_copy_ctor e la sua funzione helper make_movable() funzionano con qualsiasi object mobile ma non copiabile. Per accedere all'object avvolto, utilizzare l' operator()() .

Uscita prevista:

 valore: 1
 object ancora non cancellato
 valore: 1
 Distruggere 000000DFDD172280
 object è stato cancellato

Bene, l'indirizzo del puntatore può variare. 😉

dimostrazione

Questo sembra funzionare su gcc4.8

 #include  #include  struct Foo {}; void bar(std::unique_ptr p) { std::cout << "bar\n"; } int main() { std::unique_ptr p(new Foo); auto f = [ptr = std::move(p)]() mutable { bar(std::move(ptr)); }; f(); return 0; } 

In ritardo, ma come alcune persone (incluso me) sono ancora bloccate su c ++ 11:

Ad essere onesti, non mi piacciono molto le soluzioni pubblicate. Sono sicuro che funzioneranno, ma richiedono un sacco di roba aggiuntiva e / o syntax std::bind syntax … e non penso che valga la pena lo sforzo per una soluzione così temporanea che verrà comunque rifatta- l’aggiornamento a c ++> = 14. Quindi penso che la soluzione migliore sia quella di evitare la cattura del movimento per c ++ 11 completamente.

Di solito la soluzione più semplice e più leggibile è usare std::shared_ptr , che è ansible copiare e quindi la mossa è completamente evitabile. Il rovescio della medaglia è che è un po ‘meno efficiente, ma in molti casi l’efficienza non è così importante.

 // myPointer could be a parameter or something std::unique_ptr myPointer(new int); // convert/move the unique ptr into a shared ptr std::shared_ptr mySharedPointer( std::move(myPointer) ); std::function = [mySharedPointer](){ *mySharedPointer = 4; }; // at end of scope the original mySharedPointer is destroyed, // but the copy still lives in the lambda capture. 

.

Se si verifica il caso molto raro, è davvero necessario move il puntatore (ad esempio, se si desidera eliminare esplicitamente un puntatore in un thread separato a causa della lunga durata dell’eliminazione, o la prestazione è assolutamente cruciale), questo è praticamente l’unico caso in cui io ancora usa i puntatori grezzi in c ++ 11. Questi sono ovviamente anche copiabili.

Solitamente contrassegno questi rari casi con un //FIXME: per garantire che sia refactored una volta aggiornato a c ++ 14.

 // myPointer could be a parameter or something std::unique_ptr myPointer(new int); //FIXME:c++11 upgrade to new move capture on c++>=14 // "move" the pointer into a raw pointer int* myRawPointer = myPointer.release(); // capture the raw pointer as a copy. std::function = [myRawPointer](){ *myRawPointer = 4; // ... delete myRawPointer; }; // ensure that the pointer's value is not accessible anymore after capturing myRawPointer = nullptr; 

Sì, i puntatori grezzi sono piuttosto malvisti in questi giorni (e non senza ragione), ma penso davvero che in questi casi rari (e temporanei!) Siano la soluzione migliore.