2012-04-04 22 views
17

Mientras trabajaba en this question, noté que la implementación de std::function de GCC (v4.7) mueve sus argumentos cuando se toman por valor. El código siguiente muestra este comportamiento:¿Está permitido `` std :: function` mover sus argumentos?

#include <functional> 
#include <iostream> 

struct CopyableMovable 
{ 
    CopyableMovable()      { std::cout << "default" << '\n'; } 
    CopyableMovable(CopyableMovable const &) { std::cout << "copy" << '\n'; } 
    CopyableMovable(CopyableMovable &&)  { std::cout << "move" << '\n'; } 
}; 

void foo(CopyableMovable cm) 
{ } 

int main() 
{ 
    typedef std::function<void(CopyableMovable)> byValue; 

    byValue fooByValue = foo; 

    CopyableMovable cm; 
    fooByValue(cm); 
} 
// outputs: default copy move move 

Vemos aquí que se realiza una copia de cm (lo que parece razonable, ya que el parámetro byValue 's se toma por valor), pero entonces hay dos movimientos. Como function está funcionando en una copia de cm, el hecho de que mueva su argumento puede verse como un detalle de implementación sin importancia. Sin embargo, este comportamiento hace que algunos problemas when using function together with bind:

#include <functional> 
#include <iostream> 

struct MoveTracker 
{ 
    bool hasBeenMovedFrom; 

    MoveTracker() 
     : hasBeenMovedFrom(false) 
    {} 
    MoveTracker(MoveTracker const &) 
     : hasBeenMovedFrom(false) 
    {} 
    MoveTracker(MoveTracker && other) 
     : hasBeenMovedFrom(false) 
    { 
     if (other.hasBeenMovedFrom) 
     { 
      std::cout << "already moved!" << '\n'; 
     } 
     else 
     { 
      other.hasBeenMovedFrom = true; 
     } 
    } 
}; 

void foo(MoveTracker, MoveTracker) {} 

int main() 
{ 
    using namespace std::placeholders; 
    std::function<void(MoveTracker)> func = std::bind(foo, _1, _1); 
    MoveTracker obj; 
    func(obj); // prints "already moved!" 
} 

¿Este comportamiento es permitido por la norma? ¿Está permitido std::function mover sus argumentos? Y si es así, ¿es normal que podamos convertir el contenedor devuelto por bind en un std::function con parámetros de valor por valor agregado, aunque esto desencadena un comportamiento inesperado cuando se trata de varias ocurrencias de marcadores de posición?

+0

Me parece que el problema es más con marcadores de posición que 'std :: function'. A saber, el hecho de que al crear un "empate", el movimiento se utiliza desde el argumento original a ambos resultados esperados. –

+0

Curiosamente, el compilador de Visual C++ 11 imprime "movimiento de copia predeterminado" en el primer ejemplo y no imprime "ya movido". en el segundo. Me pregunto si este movimiento adicional puede provenir del funcionamiento interno de std :: function y/o el reenvío perfecto. –

+0

@MatthieuM. ¿Podrías elaborar? No estoy muy familiarizado con la implementación de marcadores de posición. Si el problema proviene de los marcadores de posición, ¿cómo es posible que no surja el problema al usar 'auto' para deducir el tipo" bind-wrapper ", en lugar de usar' std :: function'? –

Respuesta

16

std::function se especifica para pasar los argumentos suministrados a la función envuelta con std::forward. p.ej. para std::function<void(MoveTracker)>, el operador de llamada de función es equivalente a

void operator(CopyableMovable a) 
{ 
    f(std::forward<CopyableMovable>(a)); 
} 

Desde std::forward<T> es equivalente a std::move cuando T no es un tipo de referencia, esto representa uno de los movimientos en el primer ejemplo. Es posible que el segundo proceda de tener que pasar por las capas de indirección dentro de std::function.

Esto entonces también explica el problema que está teniendo con el uso de std::bind como la función envuelta: std::bind es también especificada para que presente sus parámetros, y en este caso es que se pasa una referencia rvalue resultante de la convocatoria std::forward interior std::function. El operador de llamada de función de su expresión de vinculación está reenviando una referencia rvalue a cada uno de los argumentos. Lamentablemente, como ha reutilizado el marcador de posición, se trata de una referencia de valor real para el mismo objeto en ambos casos, por lo que para los tipos móviles, cualquiera que se construya primero moverá el valor y el segundo parámetro obtendrá un depósito vacío.

+1

¡Oh, nunca me di cuenta de que 'std :: forward ' era equivalente a 'std :: move '! Eso explica muchas cosas. –

Cuestiones relacionadas