web-dev-qa-db-fra.com

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. Des idées?

METTRE À JOUR:

Comme suggéré, une nouvelle syntaxe est nécessaire pour spécifier explicitement que nous devons transférer la propriété au 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-ce un bon candidat?

MISE À JOUR 1:

Je vais montrer mon implémentation de move et 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.
}
41
Earth Engine

Ce problème est résolu 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);});
60
mattnewport

Vous ne pouvez pas capturer définitivement un unique_ptr dans une lambda. En effet, si vous voulez capturer quoi que ce soit de façon permanente dans un lambda, cela doit être copiable; simplement mobile est insuffisant.

Cela pourrait être considéré comme un défaut dans C++ 11, mais vous auriez besoin d'une syntaxe pour dire explicitement que vous vouliez déplacer le unique_ptr valeur dans le lambda. La spécification C++ 11 est formulée très soigneusement pour empêcher les mouvements implicites sur les variables nommées; c'est pourquoi std::move existe, et c'est une chose bonne.

Pour faire ce que vous voulez, vous devrez utiliser std::bind (qui serait semi-alambiqué, nécessitant une courte séquence de binds) ou renvoyant simplement un ancien objet normal.

Aussi, ne prenez jamais unique_ptr par &&, sauf si vous écrivez réellement son constructeur de déplacement. Prenez-le simplement par valeur; la seule façon dont un utilisateur peut la fournir par valeur est avec un std::move. En effet, c'est généralement une bonne idée de ne jamais rien prendre par &&, sauf si vous écrivez le constructeur de déplacement/l'opérateur d'affectation (ou implémentez une fonction de transfert).

31
Nicol Bolas

La solution "semi-alambiquée" utilisant std::bind comme l'a mentionné Nicol Bolas n'est pas si mal 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));
}
18
marton78

Une solution sous-optimale qui a fonctionné pour moi était de convertir le unique_ptr à un shared_ptr puis capturez le shared_ptr dans la 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();
    };
}
10
tcb

J'ai utilisé cette solution de contournement vraiment douteuse, qui implique de coller le unique_ptr À l'intérieur d'un shared_ptr. En effet, mon code nécessitait un unique_ptr (En raison d'une restriction API), je ne pouvais donc pas le convertir en un shared_ptr (Sinon je ne pourrais jamais obtenir mon unique_ptr Retour).

Ma justification pour utiliser cette abomination est que c'était pour mon code de test, et j'ai dû std::bind Un unique_ptr Dans l'appel de 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);
});

Maintenant, appeler fnTest() appellera run_test() tout en lui passant le unique_ptr. Appeler fnTest() une deuxième fois entraînera un échec d'assertion, car unique_ptr A déjà été déplacé/perdu lors du premier appel.

1
Malvineous