177 votes

Déplacer la capture lambda

Comment puis-je capturer par des mouvements(aussi connu comme la référence rvalue) en C++11 lambda?

Je suis en train d'écrire quelque chose comme ceci(pas de code, mais idée similaire):

std::unique_ptr<int> myPointer(new int);

std::function<void(void)> = [std::move(myPointer)]{
   (*myPointer) = 4;
};

189voto

Ralph Tandetzky Points 5310

Généralisée lambda capture en C++14

En C++14, nous aurons la dite généralisée lambda de capture. Cela permet de déplacer la capture. Les éléments suivants seront juridiques code 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 il est beaucoup plus générale dans le sens qui a capturé les variables 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 helper types. Heureusement, le Cliquetis 3.4 compilateur déjà implémente cette fonctionnalité impressionnante. Le compilateur sera publié en décembre 2013 ou janvier 2014, si la récente libération rythme sera maintenu.

Mise à JOUR: Le Bruit 3.4 compilateur a été publié le 6 janvier 2014 avec le dit fonctionnalité.

Une solution de contournement pour déplacer la capture

Voici une mise en œuvre d'une fonction d'assistance make_rref qui aide artificielle déplacer capture

#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 la fonction qui a été exécuté 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 que l' lambda est copiable et quand copié l'affirmation dans le constructeur de copie d' rref_impl d'échec conduisant à une exécution bug. Les éléments suivants pourraient être un meilleur et encore plus de solution générique, car le compilateur de capture de l'erreur.

L'émulation généralisée lambda capture en C++11

Voici encore une idée, sur la façon de mettre en œuvre généralisée lambda de capture. L'utilisation de la fonction capture() (dont la mise en œuvre est trouvé plus bas) est comme suit:

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

Ici, lambda est un foncteur de l'objet (presque une vraie lambda) qui a capturé std::move(p) qu'il est adopté à l' capture(). Le deuxième argument de l' capture est un lambda qui prend la capture de variable comme argument. Lors de l' lambda est utilisé comme un objet de fonction, puis tous les arguments qui lui sont passés, elle sera transmise à l'interne lambda comme arguments après la capture de variable. (Dans notre cas, il n'y a pas d'autres arguments à être transmis). Essentiellement, le même que dans la solution précédente qui se passe. Voici comment capture est mis en place:

#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 de la lambda, si la capture n'est pas copiable. Dans la première solution qui ne peut être vérifié au moment de l'exécution avec un assert().

80voto

marton78 Points 1067

Vous pouvez également utiliser std::bind de la capture de l' unique_ptr:

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

25voto

Maristic Points 533

Vous pouvez obtenir la plupart de ce que vous voulez l'aide d' std::bind, comme ceci:

std::unique_ptr<int> myPointer(new int{42});

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

Le truc c'est que au lieu de capturer votre seul objet dans la capture de la liste, nous font un argument et ensuite utiliser l'application partielle via std::bind à le faire disparaître. Notez que le lambda prend par référence, parce que c'est en fait stockée dans la liaison de l'objet. J'ai aussi ajouté le code qui écrit pour le réel de l'objet mobile, parce que c'est quelque chose que vous voulez faire.

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

std::unique_ptr<int> myPointer(new int{42});

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

Mais ce code ne pas acheter quelque chose que vous n'aviez pas en C++11, via std::bind. (Il y a des situations où généralisée lambda de capture est plus puissant, mais pas dans ce cas.)

Maintenant, il ya juste un problème; vous avez voulu mettre cette fonction en std::function, mais cette classe exige que la fonction CopyConstructible, mais il ne l'est pas, c'est seulement MoveConstructible parce que c'est le stockage d'un std::unique_ptr ce qui n'est pas CopyConstructible.

Vous pour contourner le problème avec une classe wrapper et un autre niveau d'indirection, mais peut-être que vous n'avez pas besoin d' std::function . Selon vos besoins, vous pouvez être en mesure d'utiliser std::packaged_task; il faudrait faire le même travail que l' std::function, mais il n'a pas besoin de la fonction d'être copiable, seulement mobiliers (de la même manière, std::packaged_task seulement mobiliers). L'inconvénient est que parce qu'il est destiné à être utilisé en conjonction avec std::à l'avenir, vous ne pouvez faire qu'une seule fois.

Voici un petit programme qui affiche l'ensemble de ces concepts.

#include <functional>   // for std::bind
#include <memory>       // for std::unique_ptr
#include <utility>      // for std::move
#include <future>       // for std::packaged_task
#include <iostream>     // printing
#include <type_traits>  // for std::result_of
#include <cstddef>

void showPtr(const char* name, const std::unique_ptr<size_t>& 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 <typename F>
class shared_function : public std::shared_ptr<F> {
public:
    using std::shared_ptr<F>::shared_ptr;

    template <typename ...Args>
    auto operator()(Args&&...args) const
        -> typename std::result_of<F(Args...)>::type
    {
        return (*(this->get()))(std::forward<Args>(args)...);
    }
};

template <typename F>
shared_function<F> make_shared_fn(F&& f)
{
    return shared_function<F>{
        new typename std::remove_reference<F>::type{std::forward<F>(f)}};
}


int main()
{
    std::unique_ptr<size_t> 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<size_t>& 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, e.g., 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<void()> 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<void()> 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 courir et jouer avec le code.

Voici quelques exemples de sortie...

- &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 tas de sites qui se réutilisés, montrant que l' std::unique_ptr fonctionne correctement. Vous voyez aussi la fonction elle-même se déplacer lorsque nous ranger dans un wrapper de nous nourrir std::function.

Si nous passons à l'aide d' std::packaged_task, il est de 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

nous voyons donc que la fonction a été déplacé, mais plutôt que de m'installer sur le tas, c'est à l'intérieur de l' std::packaged_task c'est sur la pile.

Espérons que cette aide!

9voto

Kerrek SB Points 194696

Vous ne pouvez pas le faire. Les Lambdas ne peut capturer par copie ou par des non-constante (lvalue) de référence.

Vous pouvez construire votre propre version de “std::rref” si vous avez besoin d'; qui a été fait avant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X