2012-04-04 4 views
17

Mentre lavoravo su this question, ho notato che l'implementazione di GCC (v4.7) di std::function sposta i suoi argomenti quando sono presi per valore. Il codice seguente illustra questo comportamento:È `std :: function` permesso di spostare i suoi argomenti?

#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 

Vediamo qui che viene eseguita una copia di cm (che sembra ragionevole dal parametro s' il byValue è presa dal valore), ma poi ci sono due mosse. Poiché function funziona su una copia di cm, il fatto che si sposti il ​​suo argomento può essere visto come un dettaglio di implementazione non importante. Tuttavia, questo comportamento causa qualche guaio 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!" 
} 

È questo comportamento consentito dalla norma? std::function è consentito spostare i suoi argomenti? E se è così, è normale che possiamo convertire il wrapper restituito da bind in un std::function con i parametri di valore, anche se questo innesca un comportamento imprevisto quando si ha a che fare con più occorrenze di segnaposto?

+0

Mi sembra che il problema riguardi più i segnaposti rispetto a 'std :: function'. Vale a dire, il fatto che durante la creazione di un 'tie', lo spostamento viene utilizzato dall'argomento originale ad entrambi gli output previsti. –

+0

È interessante notare che il compilatore Visual C++ 11 stampa "copia di default" nel primo esempio e non stampa "già spostato!" nel secondo. Mi chiedo se questa ulteriore mossa possa provenire da meccanismi interni di std :: function e/o di forwarding perfetto. –

+0

@MatthieuM. Potresti elaborare? Non ho molta familiarità con l'implementazione dei segnaposto. Se il problema deriva dai segnaposto, come mai il problema non si pone quando si utilizza 'auto' per dedurre il tipo" bind-wrapper ", invece di usare' std :: function'? –

risposta

16

std::function specificato per passare gli argomenti forniti alla funzione di avvolgimento con std::forward. per esempio. per std::function<void(MoveTracker)>, l'operatore chiamata di funzione è equivalente a

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

Poiché std::forward<T> equivale a std::move quando T non è un tipo di riferimento, questo rappresenta una delle mosse nel primo esempio. È possibile che il secondo provenga dal dover passare attraverso i livelli di riferimento indiretto all'interno di std::function.

Questo poi spiega anche il problema si è verificato con l'utilizzo std::bind come funzione avvolto: std::bind è anche specificato per inoltrare i parametri, e in questo caso viene passato un riferimento rvalue risultante dalla std::forward chiamata all'interno std::function. L'operatore di chiamata di funzione dell'espressione di bind inoltra quindi un riferimento di rvalue a ciascuno degli argomenti. Sfortunatamente, dal momento che hai riutilizzato il segnaposto, in entrambi i casi si tratta di un riferimento di rvalue allo stesso oggetto, quindi per i tipi mobili, a seconda di quale dei due è costruito per primo, il valore verrà spostato e il secondo parametro otterrà una shell vuota.

+1

Oh, non ho mai capito che 'std :: forward ' era equivalente a 'std :: move '! Questo spiega molte cose. –