49 votes

Comment capturer un unique_ptr dans une expression lambda ?

J'ai essayé ce qui suit :

std::function<void ()> getAction(std::unique_ptr<MyClass> &&psomething){
    //The caller given ownership of psomething
    return [psomething](){ 
        psomething->do_some_thing();
        //psomething is expected to be released after this point
    };
}

Mais il ne compile pas. Avez-vous des idées ?

UPDATE :

Comme suggéré, une nouvelle syntaxe est nécessaire pour spécifier explicitement que nous devons transférer la propriété à la lambda, je pense maintenant à la syntaxe suivante :

std::function<void ()> getAction(std::unique_ptr<MyClass> psomething){
    //The caller given ownership of psomething
    return [auto psomething=move(psomething)](){ 
        psomething->do_some_thing();
        //psomething is expected to be released after this point
    };
}

Serait-il un bon candidat ?

UPDATE 1 :

Je vais montrer mon implémentation de move y copy comme suit :

template<typename T>
T copy(const T &t) {
    return t;
}

//process lvalue references
template<typename T>
T move(T &t) {
    return std::move(t);
}

class A{/*...*/};

void test(A &&a);

int main(int, char **){
    A a;
    test(copy(a));    //OK, copied
    test(move(a));    //OK, moved
    test(A());        //OK, temporary object
    test(copy(A()));  //OK, copying temporary object
    //You can disable this behavior by letting copy accepts T &  
    //test(move(A())); You should never move a temporary object
    //It is not good to have a rvalue version of move.
    //test(a); forbidden, you have to say weather you want to copy or move
    //from a lvalue reference.
}

69voto

mattnewport Points 4809

Cette question est traitée par capture généralisée lambda en C++14 :

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

42voto

Nicol Bolas Points 133791

Vous ne pouvez pas capturer de façon permanente un unique_ptr dans un lambda. En effet, si vous voulez capturer de manière permanente quoi que ce soit dans une lambda, cela doit être copiable ; le simple fait de pouvoir se déplacer est insuffisant.

Cela pourrait être considéré comme un défaut dans C++11, mais il faudrait une syntaxe pour indiquer explicitement que l'on veut déplacer l'élément unique_ptr dans la lambda. La spécification C++11 est très soigneusement formulée pour empêcher les déplacements implicites sur les variables nommées ; c'est pourquoi std::move existe, et c'est un bon chose.

Pour faire ce que vous voulez, il faudra soit utiliser std::bind (ce qui serait semi-convolu, nécessitant une courte séquence de binds ) ou simplement retourner un vieil objet ordinaire.

Aussi, ne prenez jamais unique_ptr par && à moins que vous n'écriviez réellement son constructeur de mouvement. Il suffit de le prendre par valeur ; la seule façon pour un utilisateur de le fournir par valeur est avec un std::move . En effet, c'est généralement une bonne idée de ne jamais prendre quelque chose pour argent comptant. && sauf si vous écrivez le constructeur/opérateur d'affectation du mouvement (ou si vous implémentez une fonction de transfert).

19voto

marton78 Points 1067

La solution "semi-convolue" utilisant std::bind comme l'a mentionné Nicol Bolas n'est pas si mauvaise après tout :

std::function<void ()> getAction(std::unique_ptr<MyClass>&& psomething)
{
    return std::bind([] (std::unique_ptr<MyClass>& p) { p->do_some_thing(); },
                     std::move(psomething));
}

14voto

chakrapani bhat Points 33

Une solution sous-optimale qui a fonctionné pour moi a consisté à convertir l'adresse de l'utilisateur en une adresse électronique. unique_ptr a un shared_ptr et ensuite capturer le shared_ptr dans le lambda.

std::function<void()> getAction(std::unique_ptr<MyClass> psomething)
{
    //The caller given ownership of psomething
    std::shared_ptr<MyClass> psomethingShared = std::shared_ptr<MyClass>(std::move(psomething));
    return [psomethingShared]()
    {
        psomethingShared->do_some_thing();
    };
}

3voto

Malvineous Points 2416

J'ai utilisé cette solution de contournement vraiment douteuse, qui consiste à coller le fichier unique_ptr à l'intérieur d'un shared_ptr . C'est parce que mon code nécessitait un unique_ptr (en raison d'une restriction de l'API), je n'ai donc pas pu le convertir en un fichier de type shared_ptr (sinon je n'aurais jamais pu obtenir mon unique_ptr arrière).

Je justifie l'utilisation de cette abomination par le fait qu'il s'agissait de mon code de test, et que je devais std::bind a unique_ptr dans l'appel de la fonction de test.

// Put unique_ptr inside a shared_ptr
auto sh = std::make_shared<std::unique_ptr<Type>>(std::move(unique));

std::function<void()> fnTest = std::bind([this, sh, input, output]() {
    // Move unique_ptr back out of shared_ptr
    auto unique = std::move(*sh.get());

    // Make sure unique_ptr is still valid
    assert(unique);

    // Move unique_ptr over to final function while calling it
    this->run_test(std::move(unique), input, output);
});

J'appelle maintenant fnTest() appellera run_test() tout en passant le unique_ptr à elle. Appeler fnTest() une deuxième fois entraînera l'échec de l'assertion, parce que l'élément unique_ptr a déjà été déplacé/perdu lors du premier appel.

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