2016-03-25 22 views
5

La distinzione tra std::move e std::forward è ben nota, usiamo quest'ultima per conservare la categoria di valore di un oggetto inoltrato e la prima per eseguire il cast di riferimento di rvalue per abilitare la semantica del movimento.Perché non usare sempre std :: forward?

In efficace moderna C++, esiste una linea guida che gli Stati

uso std::move su riferimenti rvalue, std::forward su riferimenti universali.

Eppure, nello scenario seguente (e scenari in cui non vogliamo cambiare categoria valore),

template <class T> 
void f(vector<T>&& a) 
{ 
    some_func(std::move(a)); 
} 

dove a non è un riferimento di inoltro, ma un semplice riferimento rvalue, wouldn' t essere esattamente lo stesso per fare quanto segue?

template <class T> 
void f(vector<T>&& a) 
{ 
    some_func(std::forward<decltype(a)>(a)); 
} 

Da questa può essere facilmente incapsulata in una macro come questa,

#define FWD(arg) std::forward<decltype(arg)>(arg) 

non è conveniente utilizzare sempre questa macro definizione in questo modo?

void f(vector<T>&& a) 
{ 
    some_func(FWD(a)); 
} 

Non sono i due modi di scrivere questo esattamente equivalente?

+3

Ho problemi a visualizzare la soluzione macro più comoda della scrittura di 'move()' ...ma questo mi sembra una domanda basata sull'opinione pubblica. Entrambi fanno la stessa cosa – Barry

+3

ma talvolta l'argomento è un riferimento regolare e tu vuoi spostare il vettore. Continuare a usare l'inoltro non è sufficiente. –

+0

@Barry Volevo una conferma su di loro equivalente, quindi per quello. Mi piacerebbe non essere d'accordo sul fatto che siano basate sull'opinione pubblica, semplicemente difendere uno stile di programmazione non implica pregiudizi, si potrebbero avere ottime ragioni per farlo (ed essere in grado di elaborarli) –

risposta

6

Eh. Discutibile. Nel tuo particolare esempio è equivalente. Ma non vorrei diventare un'abitudine. Una ragione è perché vuoi una distinzione semantica tra l'inoltro e lo spostamento. Un altro motivo è che per avere un'API coerente dovresti avere MOV oltre a FWD, e che sembra davvero male e non fa nulla. Ancora più importante, però, è che il tuo codice può fallire in modo imprevisto.

Si consideri il seguente codice:

#include <iostream> 

using namespace std; 

#define FWD(arg) std::forward<decltype(arg)>(arg) 

struct S { 
    S() { cout << "S()\n"; } 
    S(const S&) { cout << "S(const S&)\n"; } 
    S(S&&) { cout << "S(S&&)\n"; } 
}; 

void some_func(S) {} 

void f(S&& s) 
{ 
    some_func(FWD(s)); 
} 

int main() 
{ 
    f(S{}); 
} 

Questo stampa

S()
S (S & &)

Tuttavia, se mi limito a cambiare il FWD linea per avere un'altra coppia (apparentemente opzionale) di parent eses, in questo modo:

void f(S&& s) 
{ 
    some_func(FWD((s))); 
} 

Ora arriviamo

S()
S (const S &)

E questo perché ora stiamo usando decltype su un espressione, che valuta un riferimento lvalue.

+0

Hai dei buoni punti. Ma non sono d'accordo sulla caratterizzazione "apparentemente facoltativa" per le parentesi. Se uno sta lavorando con 'decltype' dovrebbe sapere [questa roba] (http://stackoverflow.com/a/17242295/4224575) e questo è un errore fattibile anche in' std :: forward

+2

@Lorah Sì, ma è una macro - non si può immediatamente vedere che usa 'decltype'. – Barry

+0

@Barry Immagino che non vedresti cosa fa se fosse una funzione, devi sempre leggere il codice; Per lo più prendo in considerazione la "mancanza di trasparenza" relativa ai macro come "strani effetti collaterali o mutazione del comportamento" che non sono evidenti nemmeno quando si legge il codice (come valutare un'espressione due volte ecc ...). Non sono sopra l'uso di una macro per ragioni come 'FWD', cioè la sostituzione testuale (nessuna doppia valutazione, nessuna struttura di dati incidentali, nessun flusso di controllo nascosto). Potrebbe per favore dare un'occhiata a [questo] (http://stackoverflow.com/q/36230428/4224575)? (è fuori tema ma potrei usare un po 'di aiuto) –

Problemi correlati