2016-03-11 22 views
16

Sto osservando l'ottimizzazione del valore di ritorno nel caso di tuple/legami e il comportamento che osservo non è quello che mi aspettavo. Nell'esempio seguente mi aspetterei che la semantica del movimento si avvii, cosa che fa, ma rimane una operazione di copia. L'uscita dal sotto in ottimizzato è:Ottimizzazione del valore di ritorno della tupla/cravatta

Test duo output, non_reference tuple 
Default constructor invoked 
Parameter constructor invoked 
Copy constructor invoked 
Move Assignment operator invoked 
100 

L'invocazione del costruttore di copia nel rendere tupla all'interno della funzione sembra inutile. C'è un modo per rimuovere questo? Sto usando il compilatore MSVC 2012.

#include <iostream> 
#include <tuple> 

class A 
{ 
public: 
    int value; 
    A() : value(-1) 
    { 
     std::cout << "Default constructor invoked" << std::endl; 
    } 

    explicit A(const int v) : value(v) 
    { 
     std::cout << "Parameter constructor invoked" << std::endl; 
    } 

    A(const A& rhs) 
    { 
     value = rhs.value; 
     std::cout << "Copy constructor invoked" << std::endl; 
    } 

    A(const A&& rhs) 
    { 
     value = rhs.value; 
     std::cout << "Move constructor invoked" << std::endl; 
    } 

    A& operator=(const A& rhs) 
    { 
     value = rhs.value; 
     std::cout << "Assignment operator invoked" << std::endl; 
     return *this; 
    } 

    A& operator=(const A&& rhs) 
    { 
     value = rhs.value; 
     std::cout << "Move Assignment operator invoked" << std::endl; 
     return *this; 
    } 
}; 

std::tuple<A, int> return_two_non_reference_tuple() 
{ 
    A tmp(100); 

    return std::make_tuple(tmp, 99); 
} 

int main(int argc, char* argv[]) 
{ 

     std::cout << "Test duo output, non_reference tuple" << std::endl;  
     A t3; 
     int v1; 
     std::tie(t3, v1) = return_two_non_reference_tuple(); 
     std::cout << t3.value << std::endl << std::endl; 

     system("pause"); 
     return 0; 
} 

risposta

6

Il costruttore mossa non verrà chiamato automaticamente, perché si sta chiamando

std::make_tuple(tmp, 99); 

In questo caso, tmp è un lvalue. È possibile utilizzare std::move per lanciarla ad un riferimento rvalue:

return std::make_tuple(std::move(tmp), 99); 

Questo indicherà al compilatore di utilizzare il costruttore mossa.

1

Non si sposta tmp:

A tmp(100); 

return std::make_tuple(std::move(tmp), 99); 
3

La copia avviene in return_two_non_reference_tuple().

Un modo per rimuoverlo è quello di spostare tmp

std::tuple<A, int> return_two_non_reference_tuple() 
{ 
    A tmp(100); 
    return std::make_tuple(std::move(tmp), 99); 
} 

modo o nell'altro

std::tuple<A, int> return_two_non_reference_tuple() 
{ 
    return std::make_tuple(A(100), 99); 
} 
3

La copia avviene qui:

std::make_tuple(tmp, 99); 

Anche se si può vedere che tmp potrebbe essere in grado di essere costruito direttamente nella tupla, la copia dal tmp alla tupla non sarà tralasciata. Quello che vuoi veramente è un modo per passare argomenti per std::tuple da utilizzare per costruire i suoi oggetti interni A e int. Non c'è una cosa del genere per std::tuple, ma c'è un modo per ottenere lo stesso effetto.

Poiché sono disponibili solo due tipi, è possibile utilizzare std::pair. Questo ha un costruttore std::piecewise_construct, che accetta due std::tuples contenenti gli argomenti da passare ai costruttori degli oggetti interni.

std::pair<A, int> return_two_non_reference_tuple() 
{ 
    return {std::piecewise_construct, 
      std::make_tuple(100), std::make_tuple(99)}; 
} 

La cosa bella di questa soluzione è che si può ancora utilizzare std::tie al sito di chiamata, perché std::tuple ha un operatore di assegnazione da std::pair.

std::tie(t3, v1) = return_two_non_reference_tuple(); 

Come si può vedere dall'output, la copia è sparita.Non viene sostituito da un movimento come nelle altre risposte, è totalmente rimossa: uscita duo

prova, tuple non_reference

costruttore predefinito invocato

costruttore Parametro invocato

Sposta Assegnazione operatore invocato

Live Demo

+0

Grazie, è una buona alternativa per il caso semplice, ma se ci sono più azioni fatte su tmp e più uscite, non funzionerebbe. E non penso che funzionerebbe a MSVC 2012. – thorsan

+0

Copia elision sta accadendo qui. Il problema è che 'tmp' non viene spostato nella' tupla'. Il costruttore di copie sta copiando 'tmp' nella tupla, non la tupla copiata. – Simple

+0

@Simple lo so, forse la mia risposta non è abbastanza chiara. – TartanLlama