Capture lambda généralisée en C ++ 14
En C ++ 14, nous aurons la soi-disant capture lambda généralisée . Cela permet la capture de mouvement. Ce qui suit sera le code juridique en C ++ 14:
using namespace std;
// a unique_ptr is move-only
auto u = make_unique<some_type>( 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 dans le sens où les variables capturées peuvent être initialisées avec quelque chose comme ceci:
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'assistance. Heureusement, le compilateur Clang 3.4 implémente déjà cette fonctionnalité géniale. Le compilateur sortira en décembre 2013 ou janvier 2014, si le rythme de publication récent est maintenu.
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'assistance make_rref
qui aide à la capture de mouvement artificiel
#include <cassert>
#include <memory>
#include <utility>
template <typename T>
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<typename T> rref_impl<T> make_rref( T && x )
{
return rref_impl<T>{ 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<int> p{new int(0)};
auto rref = make_rref( std::move(p) );
auto lambda =
[rref]() mutable -> std::unique_ptr<int> { return rref.move(); };
assert( lambda() );
assert( !lambda() );
}
L'inconvénient ici est qu'il lambda
est copiable et lorsqu'il est copié, l'assertion dans le constructeur de copie derref_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 de la capture lambda généralisée en C ++ 11
Voici une autre idée, sur la façon d'implémenter 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 <cassert>
#include <memory>
int main()
{
std::unique_ptr<int> p{new int(0)};
auto lambda = capture( std::move(p),
[]( std::unique_ptr<int> & p ) { return std::move(p); } );
assert( lambda() );
assert( !lambda() );
}
Voici lambda
un objet foncteur (presque un vrai lambda) qui a capturé std::move(p)
comme il est passé à capture()
. Le deuxième argument de capture
est un lambda qui prend la variable capturée comme argument. Quand lambda
est utilisé comme objet fonction, tous les arguments qui lui sont passés seront transmis au lambda interne en tant qu'arguments après la variable capturée. (Dans notre cas, il n'y a plus d'arguments à transmettre). Essentiellement, la même chose que dans la solution précédente se produit. Voici comment capture
est implémenté:
#include <utility>
template <typename T, typename F>
class capture_impl
{
T x;
F f;
public:
capture_impl( T && x, F && f )
: x{std::forward<T>(x)}, f{std::forward<F>(f)}
{}
template <typename ...Ts> auto operator()( Ts&&...args )
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
template <typename ...Ts> auto operator()( Ts&&...args ) const
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
};
template <typename T, typename F>
capture_impl<T,F> capture( T && x, F && f )
{
return capture_impl<T,F>(
std::forward<T>(x), std::forward<F>(f) );
}
Cette deuxième 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 qu'au moment de l'exécution avec un fichier assert()
.
moveCapture
wrapper pour les passer en arguments (cette méthode est utilisée ci-dessus et dans Capn'Proto, une bibliothèque du créateur de protobuffs) soit simplement accepter que vous ayez besoin de compilateurs qui le supportent: PVous pouvez également utiliser
std::bind
pour capturerunique_ptr
:la source
unique_ptr
référence rvalue ne peut pas se lier à unint *
.myPointer
dans ce cas). Par conséquent, le code ci-dessus ne se compile pas dans VS2013. Cela fonctionne bien dans GCC 4.8.Vous pouvez réaliser la plupart de ce que vous voulez en utilisant
std::bind
, comme ceci:L'astuce ici est qu'au lieu de capturer votre objet de mouvement uniquement dans la liste des captures, nous en faisons un argument puis 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 en fait stocké dans l'objet de liaison. J'ai également ajouté du code qui écrit sur l'objet mobile réel, car c'est quelque chose que vous voudrez peut-être faire.En C ++ 14, vous pouvez utiliser la capture lambda généralisée pour atteindre les mêmes fins, avec ce code:
Mais ce code ne vous achète rien que vous n'aviez pas en C ++ 11 via
std::bind
. (Dans certaines situations, 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 a
std::function
, mais cette classe nécessite que la fonction soit CopyConstructible , mais ce n'est pas le cas, c'est uniquement MoveConstructible car elle stocke unstd::unique_ptr
qui n'est pas CopyConstructible .Vous devez contourner le problème avec la classe wrapper et un autre niveau d'indirection, mais peut-être n'en avez-vous pas besoin
std::function
du tout. Selon vos besoins, vous pourrez peut-être utiliserstd::packaged_task
; il ferait le même travail questd::function
, mais il ne nécessite pas que la fonction soit copiable, seulement déplaçable (de même,std::packaged_task
n'est que déplaçable). L'inconvénient est que, comme il est destiné à être utilisé avec std :: future, vous ne pouvez l'appeler qu'une seule fois.Voici un petit programme qui montre tous ces concepts.
J'ai mis un programme ci-dessus sur Coliru , afin que vous puissiez exécuter et jouer avec le code.
Voici une sortie typique ...
Vous voyez les emplacements de tas réutilisés, ce qui montre que le
std::unique_ptr
fonctionne correctement. Vous voyez également la fonction elle-même se déplacer lorsque nous la stockons dans un wrapper auquel nous alimentonsstd::function
.Si nous passons à l'utilisation
std::packaged_task
, la dernière partie devientdonc nous voyons que la fonction a été déplacée, mais plutôt que d'être déplacée sur le tas, c'est à l'intérieur du
std::packaged_task
qui est sur la pile.J'espère que cela t'aides!
la source
En retard, mais comme certaines personnes (dont moi) sont toujours bloquées sur c ++ 11:
Pour être honnête, je n'aime vraiment aucune des solutions publiées. Je suis sûr qu'ils fonctionneront, mais ils nécessitent beaucoup de choses supplémentaires et / ou cryptiques
std::bind
syntaxe ... et je ne pense pas que cela en vaille la peine pour une telle solution temporaire qui sera de toute façon refactorisée lors de la mise à niveau vers c ++> = 14. Je pense donc que la meilleure solution est d'éviter complètement la capture de mouvement pour C ++ 11.Habituellement, la solution la plus simple et la mieux lisible est d'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..
Si le cas très rare se produit, il est vraiment obligatoire de
move
le pointeur (par exemple, vous voulez supprimer explicitement un pointeur dans un thread séparé en raison d'une longue durée de suppression, ou les performances sont absolument cruciales), c'est à peu près le seul cas où j'utilise encore pointeurs bruts en c ++ 11. Ceux-ci sont bien sûr également copiables.Habituellement, je marque ces rares cas avec un
//FIXME:
pour m'assurer qu'il est remanié une fois la mise à niveau vers C ++ 14.Oui, les pointeurs bruts sont assez mal vus ces jours-ci (et non sans raison), mais je pense vraiment que dans ces cas rares (et temporaires!), Ils sont la meilleure solution.
la source
Je regardais ces réponses, mais j'ai trouvé que bind était difficile à lire et à comprendre. Donc, ce que j'ai fait, c'est créer une classe qui se déplace sur copie à la place. De cette façon, il est explicite avec ce qu'il fait.
La
move_with_copy_ctor
classe et sa fonction d'assistance fonctionnerontmake_movable()
avec n'importe quel objet déplaçable mais non copiable. Pour accéder à l'objet encapsulé, utilisez leoperator()()
.Production attendue:
Eh bien, l'adresse du pointeur peut varier. ;)
Demo
la source
Cela semble fonctionner sur gcc4.8
la source