2011-11-23 8 views
30

he intentado lo siguiente:Cómo capturar un unique_ptr en una expresión lambda?

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

Pero no compila. ¿Algunas ideas?

ACTUALIZACIÓN:

como se sugiere, se requiere alguna nueva sintaxis para especificar explícitamente que necesitamos para transferir la propiedad a la lambda, ahora estoy pensando en la siguiente sintaxis:

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

¿Sería un buen candidato?

ACTUALIZACIÓN 1:

voy a mostrar mi aplicación de move y copy de la siguiente manera:

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

Respuesta

46

Este problema se soluciona lambda generalized capture 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);}); 
+0

Esa es la solución ideal. –

+0

¿Cuándo se libera el puntero único en estas circunstancias? Cuando la lambda lo hace? – Leo

+0

@Leo El puntero único se debe mover al bloque de datos de la lambda (una lambda es solo un objeto con el operador '()') y luego se libera después de que se libera el objeto lambda. El objeto lambda como valor r puede liberarse después de 'go.run' si no se mueve. Si se mueve, puede liberarse en cualquier momento después de esto, y esto tiene sentido si desea programar 'do_something_with' para que se ejecute más tarde. –

31

No se puede capturar de forma permanente un unique_ptr en una lambda. De hecho, si desea capturar de forma permanente cualquier elemento en una lambda, debe ser copiable; simplemente movible es insuficiente.

Esto podría considerarse un defecto en C++ 11, pero necesitaría cierta sintaxis para indicar explícitamente que desea mover el valor unique_ptr a la lambda. La especificación C++ 11 está redactada con mucho cuidado para evitar movimientos implícitos en las variables nombradas; es por eso que existe std::move, y esta es una buena cosa .

Para hacer lo que desee se requerirá utilizar std::bind (que sería semi-intrincado, que requiere una secuencia corta de binds) o simplemente devolver un objeto viejo normal.

Además, nunca tome unique_ptr por &&, a menos que realmente esté escribiendo su constructor de movimientos. Solo tómalo por valor; la única forma en que un usuario puede proporcionarlo por valor es con un std::move. De hecho, generalmente es una buena idea no tomar nada por &&, a menos que esté escribiendo el operador de asignación/constructor de movimiento (o implementando una función de reenvío).

+0

Greate! Me gustaría marcar esto como una respuesta correcta si proporciona algún ejemplo de la secuencia 'bind '. –

+2

Acepto que tomar 'unique_ptr' por' && 'no es una buena idea. Sin embargo, en general creo que tomar algo por '&&' significa "Quiero que la persona que llama en realidad me dé la propiedad de algo, no solo como referencia". –

+0

@EarthEngine: Puedes obtenerlo tomando el argumento por * valor *. Si pasan un temporal, entonces el temporal se moverá a su valor de argumento. Si pasan de forma no temporal, todavía tienen que usar 'std :: move', lo que hará que el movimiento pase a su * argumento *. La forma en que lo hace significa que su función no * tiene * para apropiarse de ella. [Mi publicación aquí] (http://stackoverflow.com/questions/8114276/how-do-i-pass-a-unique-ptr-argument-to-a-constructor-or-a-function/8114913#8114913) explica esto en mayor detalle. –

18

La solución "semi-enrevesado" usando std::bind como se ha mencionado por Nicol Bolas no es tan malo después de todo:

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

Estoy de acuerdo, no está mal. –

+1

no se compila en C++ 11 – ZivS

+0

Como se requiere que std :: function se pueda copiar, no puedo ver cómo funcionaría. – Catskul

7

Una solución subóptima que funcionó para mí fue convertir el unique_ptr en un shared_ptr y luego capturar el shared_ptr en el 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(); 
    }; 
} 
0

que utilizan esta solución muy poco fiables, lo que implica que se pega la unique_ptr dentro de un shared_ptr. Esto se debe a que mi código requería un unique_ptr (debido a una restricción de la API), así que no pude convertirlo realmente a shared_ptr (de lo contrario, nunca podría recuperar mi unique_ptr).

Mi justificación para usar esta abominación es que era para mi código de prueba, y tuve que std::bind a unique_ptr en la llamada a la función de prueba.

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

Ahora llamando fnTest() llamará run_test() al pasar el unique_ptr a ella. Llamar al fnTest() por segunda vez dará como resultado una falla de aserción, porque el unique_ptr ya se ha movido/perdido durante la primera llamada.

Cuestiones relacionadas