Déplacer la capture dans lambda

Comment puis-je capturer par mouvement (également appelé référence rvalue) dans un lambda C ++ 11?

J’essaie d’écrire quelque chose comme ceci:

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

Capture lambda généralisée en C ++ 14

En C ++ 14, nous aurons la capture lambda généralisée . Cela permet la capture de mouvement. Le code suivant sera en 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 ); } ); 

Mais c’est beaucoup plus général en ce sens que les variables capturées peuvent être initialisées avec n’importe quoi:

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

En C ++ 11, ce n’est pas encore possible, mais avec quelques astuces qui impliquent des types d’aide. Heureusement, le compilateur Clang 3.4 implémente déjà cette fonctionnalité géniale. Le compilateur sortira en décembre 2013 ou en janvier 2014, si le rythme de publication récent sera conservé.

MISE À JOUR: Le compilateur Clang 3.4 a été publié le 6 janvier 2014 avec ladite fonctionnalité.

Une solution de contournement pour la capture de mouvement

Voici une implémentation d’une fonction d’aide make_rref qui aide à la capture de mouvement artificielle

 #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) }; } 

Et voici un cas de test pour cette fonction qui a fonctionné avec succès sur mon 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() ); } 

L’inconvénient est que lambda est copiable et que, lors de la copie, l’assertion dans le constructeur de copie de rref_impl échoue, conduisant à un bogue d’exécution. Ce qui suit pourrait être une solution meilleure et encore plus générique car le compilateur détectera l’erreur.

Émulation d’une capture lambda généralisée en C ++ 11

Voici une autre idée sur la mise en œuvre de la capture lambda généralisée. L’utilisation de la fonction capture() (dont l’implémentation se trouve plus bas) est la suivante:

 #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() ); } 

Ici, lambda est un object foncteur (presque un vrai lambda) qui a capturé std::move(p) fur et à mesure qu’il est passé à capture() . Le second argument de capture est un lambda qui prend la variable capturée comme argument. Lorsque lambda est utilisé comme object de fonction, tous les arguments qui lui sont transmis seront transmis à lambda interne sous forme d’arguments après la variable capturée. (Dans notre cas, il n’y a pas d’autres arguments à transmettre). Essentiellement, la même chose que dans la solution précédente se produit. Voici comment la capture est implémentée:

 #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) ); } 

Cette seconde solution est également plus propre, car elle désactive la copie du lambda si le type capturé n’est pas copiable. Dans la première solution, qui ne peut être vérifiée à l’exécution qu’avec un assert() .

Vous pouvez également utiliser std::bind pour capturer l’ unique_ptr :

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

Vous pouvez obtenir la plupart de ce que vous voulez en utilisant std::bind , comme ceci:

 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)); 

L’astuce ici est qu’au lieu de capturer votre object move-only dans la liste des captures, nous en faisons un argument, puis nous utilisons une application partielle via std::bind pour le faire disparaître. Notez que le lambda le prend par référence , car il est réellement stocké dans l’object bind. J’ai également ajouté du code qui écrit à l’object mobile réel, car c’est quelque chose que vous pourriez vouloir faire.

En C ++ 14, vous pouvez utiliser la capture lambda généralisée pour atteindre les mêmes fins, avec ce code:

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

Mais ce code ne vous achète rien en C ++ 11 via std::bind . (Il existe certaines situations où la capture lambda généralisée est plus puissante, mais pas dans ce cas.)

Maintenant, il n’y a qu’un seul problème. vous vouliez mettre cette fonction dans une fonction std::function , mais cette classe exige que la fonction soit CopyConstructible , mais ce n’est pas le cas, elle est uniquement MoveConstructible car elle stocke un std::unique_ptr qui n’est pas CopyConstructible .

Vous devez contourner le problème avec la classe wrapper et un autre niveau d’indirection, mais vous n’avez peut-être pas besoin de std::function . Selon vos besoins, vous pouvez utiliser std::packaged_task ; il ferait le même travail que std::function , mais il ne nécessite pas que la fonction soit copiable, seulement mobile (de même, std::packaged_task est seulement mobile). L’inconvénient est que parce qu’il est destiné à être utilisé conjointement avec std :: future, vous ne pouvez l’appeler qu’une seule fois.

Voici un petit programme qui montre tous ces concepts.

 #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 } 

J'ai mis le programme ci-dessus sur Coliru , de sorte que vous pouvez exécuter et jouer avec le code.

Voici quelques sorties typiques ...

 - &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 

Vous obtenez de voir des emplacements de tas étant réutilisés, montrant que le std::unique_ptr fonctionne correctement. Vous voyez également que la fonction elle-même se déplace lorsque nous la stockons dans un wrapper que nous alimentons avec std::function .

Si nous passons à l'utilisation de std::packaged_task , la dernière partie devient

 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 

donc nous voyons que la fonction a été déplacée, mais plutôt que d'être déplacée sur le tas, elle se trouve dans le std::packaged_task qui se trouve sur la stack.

J'espère que cela t'aides!

Je regardais ces réponses, mais j’ai trouvé bind difficile à lire et à comprendre. Donc, ce que j’ai fait, c’est de créer un cours sur copie. De cette façon, il est explicite avec ce qu’il fait.

 #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 classe move_with_copy_ctor et sa fonction helper make_movable() fonctionneront avec tout object mobile mais non copiable. Pour accéder à l'object encapsulé, utilisez l' operator()() .

Production attendue:

 valeur: 1
 object toujours pas supprimé
 valeur: 1
 Détruire 000000DFDD172280
 l'object a été supprimé

Eh bien, l'adresse du pointeur peut varier. 😉

Démo

Cela semble fonctionner sur 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; } 

En retard, mais comme certaines personnes (y compris moi) sont toujours bloquées sur c ++ 11:

Pour être honnête, je n’aime pas vraiment les solutions proposées. Je suis sûr qu’ils fonctionneront, mais ils nécessitent beaucoup de choses supplémentaires et / ou une syntaxe std::bind cryptique … et je ne pense pas que cela vaille la peine pour une solution aussi temporaire qui sera de toute façon modifiée quand mise à niveau vers c ++> = 14. Donc, je pense que la meilleure solution est d’éviter complètement la capture de mouvements pour c ++ 11.

Habituellement, la solution la plus simple et la plus lisible consiste à utiliser std::shared_ptr , qui sont copiables et donc le déplacement est complètement évitable. L’inconvénient est que c’est un peu moins efficace, mais dans de nombreux cas, l’efficacité n’est pas si 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. 

.

Si le cas très rare se produit, il est vraiment nécessaire de move le pointeur (par exemple, si vous souhaitez supprimer explicitement un pointeur dans un thread séparé en raison d’une longue durée de suppression, ou si la performance est absolument cruciale), utiliser des pointeurs bruts dans c ++ 11. Celles-ci sont bien sûr également copiables.

D’habitude, je marque ces rares cas avec un //FIXME: pour s’assurer qu’il est refactorisé une fois la mise à niveau vers 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; 

Oui, les pointeurs bruts sont plutôt mal vu ces jours-ci (et non sans raison), mais je pense vraiment que dans ces cas rares (et temporaires!), Ils constituent la meilleure solution.