2013-05-19 15 views
18

Possiedo un oggetto functor non strutturato che sto tentando di memorizzare come std::function all'interno di un altro oggetto. Questo oggetto è davvero pesante, quindi è contrassegnato come non copiabile, ma ha un costruttore di mosse. Tuttavia, il tentativo di costruire una funzione std ::, o assegnarlo, da un costruttore temporaneo fallisce.Can std :: function può essere spostato dal riferimento rvalue a un oggetto temporaneo temporaneo?

Ecco un esempio minimo per provocare l'errore.

// pretend this is a really heavyweight functor that can't be copied. 
struct ExampleTest 
{ 
    int x; 
    int operator()(void) const {return x*2;} 
    ExampleTest() :x(0){} 
    ExampleTest(int a) :x(a){} 

    // allow move 
    ExampleTest(ExampleTest &&other) :x(other.x) {}; 

private: // disallow copy, assignment 
    ExampleTest(const ExampleTest &other); 
    void operator=(const ExampleTest &other); 
}; 

// this sometimes stores really big functors and other times stores tiny lambdas. 
struct ExampleContainer 
{ 
    ExampleContainer(int); 
    std::function<int(void)> funct; 
}; 

/******** ERROR: 
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member 
declared in class 'ExampleTest' 
******************/ 
ExampleContainer::ExampleContainer(int x) 
    : funct(ExampleTest(x)) 
{} 

/******** ERROR: 
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member 
declared in class 'ExampleTest' 
******************/ 
int SetExample(ExampleContainer *container) 
{ 
    container->funct = ExampleTest(); 
    return container->funct(); 
} 

In una costruzione ancora più semplice, dove sto solo facendo una funzione locale, ho anche l'errore:

int ContrivedExample() 
{ 
    // extra parens to sidestep most vexing parse 
    std::function<int()> zug((ExampleTest())); 
    /*** ERROR: 'ExampleTest::ExampleTest' : cannot access private member 
     declared in class 'ExampleTest' */ 
    int troz = zug() ; 
    return troz; 
} 

Per quanto posso dire, in tutti questi casi, un dovrebbe essere passato al costruttore di funzioni come valore di rvalue. Eppure il compilatore vuole copiarli.

Cosa dà? È possibile passare oggetti functor non copiabili (ma spostabili) su un costruttore std :: function? Ci sono soluzioni alternative con i puntatori e così via, ma voglio capire cosa sta succedendo qui.

Gli errori specifici riportati sopra provengono da Visual Studio 2012 con la patch CTP C++ 11. Anche GCC 4.8 e Clang 3 cadono, con i propri messaggi di errore.

+0

Se si utilizza C++ 11, perché non utilizzare l'inizializzazione brace 'ExampleTest {}' e '= delete' invece di copy-ctor privato e assignment-op? – dyp

+0

Cosa succede quando si aggiunge un operatore di assegnazione movimento? –

+0

@DyP Nessuno dei compilatori a cui ho accesso può supportare in modo affidabile la costruzione dell'elenco inizializzatore o la sintassi '= delete'. – Crashworks

risposta

18

This object is really heavyweight, so it's marked as uncopyable, but it does have a move constructor.

Se un funtore è non copiabile, non soddisfa i requisiti necessari per essere usato con std::function. Paragrafo 20.8.11.2.1/7 delle C++ 11 specifica standard:

template<class F> function(F f); 
template <class F, class A> function(allocator_arg_t, const A& a, F f); 

7 Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R . The copy constructor and destructor of A shall not throw exceptions.

+2

L'ho notato prima, ma lo standard dice anche "Ogni call wrapper (20.8.1) deve essere MoveConstructible", e 'function' ha un costruttore di move che prende valore. Che sembra contraddittorio. – Crashworks

+0

@Crashworks: Beh, l'oggetto 'std :: function' è mosso-costruibile, ma il functor da cui si suppone lo costruisca deve essere costruibile in copia –

+9

Sebbene lo standard sia abbastanza chiaro che vuole usare la costruzione di copia Non vedo davvero alcun bisogno di richiederlo! Forse vale un rapporto sui difetti. Ho dato una rapida occhiata ai rapporti esistenti ma non ne ho visto uno che menzionava questo problema. Immagino che sia una svista quando le classi TR1 vengono importate in C++ 2011. –

2

std :: funzione può essere spostare-costruito da rvalue di un oggetto funtore. E la maggior parte delle implementazioni lo fa.

Il requisito "Il mio obiettivo deve essere costruibile con la copia" di std :: function è dovuto al suo requisito di essere costruibile con la copia. std :: Il tipo di funzione è definito solo dalla sua firma (es .: void (int)) e std :: function stessa è definita dallo standard per essere costruibile con la copia. Quindi quando copi-costruisci una funzione std ::, ha bisogno di chiamare il copy-ctor del suo target (il functor sottostante). Quindi richiede che il suo bersaglio ne abbia uno. Non ha altre scelte.

Avendo il requisito che la destinazione sia costruibile con la copia, lo standard non dice che le implementazioni devono copiare, invece di spostarsi, quando si costruisce una funzione std :: da un oggetto callable rvalue. Probabilmente l'implementazione chiamerà solo il mittente del tuo oggetto callable.


più dettagliate informazioni aggiuntive esempi e test:

Ad esempio in gcc (MSVC è simile) attuazione ctor di std :: da un qualsiasi oggetto richiamabile:

template<typename _Res, typename... _ArgTypes> 
    template<typename _Functor, typename> 
    function<_Res(_ArgTypes...)>:: 
    function(_Functor __f) 
    : _Function_base() 
    { 
     typedef _Function_handler<_Signature_type, _Functor> _My_handler; 

     // don't need to care about details below, but when it uses __f, it 
     // either uses std::move, or passes it by references 
     if (_My_handler::_M_not_empty_function(__f)) 
     { 
      _My_handler::_M_init_functor(_M_functor, std::move(__f)); 
      _M_invoker = &_My_handler::_M_invoke; 
      _M_manager = &_My_handler::_M_manager; 
     } 
    } 

passando per il valore dell'argomento di "_Functor __f" userà il suo costruttore di mosse se ne ha uno, e userà il suo costruttore di copia se non ha un agente di movimento.Come il seguente programma di test in grado di dimostrare:

int main(){ 
    using namespace std; 
    struct TFunctor 
    { 
     TFunctor() = default; 
     TFunctor(const TFunctor&) { cout << "cp ctor called" << endl; } 
     TFunctor(TFunctor&&) { cout << "mv ctor called" << endl; }; 
     void operator()(){} 
    }; 

    { //!!!!COPY CTOR of TFunctor is NEVER called in this scope 
     TFunctor myFunctor; 

     //TFunctor move ctor called here 
     function<void()> myStdFuncTemp{ std::move(myFunctor) }; 

     function<void()> myStdFunc{ move(myStdFuncTemp) }; 
    } 

    { //TFunctor copy ctor is called twice in this scope 
     TFunctor myFunctor; 

     //TFunctor copy ctor called once here 
     function<void()> myStdFuncTemp{ myFunctor }; 

     //TFunctor copy ctor called once here 
     function<void()> myStdFunc{ myStdFuncTemp }; 
    } 
} 

Infine, si potrebbe fare un unstd :: function_only_movable che ha quasi tutto lo stesso con std :: funzione, ma cancella il proprio ctor copia in modo che non ha bisogno per richiedere all'oggetto callable di destinazione di avere una copia ctor. Devi anche costruirlo solo dal valore di oggetti richiamabili.

This è la mia implementazione.

+0

Si potrebbe anche "spingilo giù da un livello" e crea intorno ad esso un involucro copiato'std :: shared_ptr ' o 'const MyFunctor &' fornendo 'operator()' quindi costruisce 'std :: function' da quel wrapper. –

Problemi correlati