2012-10-29 9 views
9

Ecco una possibile definizione di std::swap:Su attuazione std :: scambio in termini di assegnazione di movimento e spostare costruttore

template<class T> 
void swap(T& a, T& b) { 
    T tmp(std::move(a)); 
    a = std::move(b); 
    b = std::move(tmp); 
} 

Credo che

  1. std::swap(v,v) è garantito per avere alcun effetto e
  2. std::swap può essere implementato come sopra.

La seguente citazione mi sembra implicare che queste convinzioni siano contraddittorie.

17.6.4.9 Argomenti delle funzioni [res.on.arguments]

1 Ciascuno dei seguenti si applica a tutti gli argomenti a funzioni definite nella libreria standard C++, se non diversamente specificato.

...

  • Se un argomento funzione si lega ad un parametro di riferimento rvalue, l'applicazione può supporre che questo parametro è un riferimento unico per questo argomento. [Nota: se il parametro è un parametro generico del modulo & & e un lvalue di tipo A è associato, l'argomento si lega a un riferimento di lv lvalue (14.8.2.1) e pertanto non è coperto dalla precedente frase . - nota finale] [Nota: se un programma esegue un lvalue su un valore durante il passaggio di tale valore a una funzione di libreria (ad esempio tramite chiamando la funzione con l'argomento move (x)), il programma è chiedendo effettivamente tale funzione a tratta quel lvalue come temporaneo. L'implementazione è gratuita per ottimizzare i controlli di aliasing che potrebbero essere necessari se l'argomento era un lvalue. -endnote]

(grazie a Howard Hinnant per providing the quote)

Lasciate v essere un oggetto di un certo tipo mobile tratto dalla Standard Template Library e considerano la chiamata std::swap(v, v). Nella riga a = std::move(b); sopra, è il caso all'interno di T::operator=(T&& t) quello this == &b, quindi il parametro è non un riferimento univoco. Si tratta di una violazione del requisito sopra riportato, pertanto la riga a = std::move(b) richiama il comportamento non definito quando viene chiamato da std::swap(v, v).

Qual è la spiegazione qui?

+0

* Qual è la spiegazione? * Cos'è che deve essere spiegata? –

+0

@ DavidRodríguez-dribeas Ho assunto due cose e sono arrivato ad una contraddizione - 'std :: move (v, v)' è garantito per non fare nulla e inoltre è un comportamento indefinito. Queste due affermazioni non possono essere entrambe corrette. Quindi qualcosa non va nel mio ragionamento o nelle ipotesi. Qual è la cosa sbagliata? –

+0

Intendi 'std :: swap (v, v)', non 'std :: move (v, v)', giusto? –

risposta

0

credo che non è una definizione valida di std::swap perché std::swap è definito a prendere i riferimenti lvalue, non i riferimenti rvalue (20.2.2 [utility.swap])

+0

Grazie per aver segnalato l'errore. Ho aggiornato la domanda. Tuttavia, la correzione dell'errore non mi sembra di rispondere al problema. –

+1

Stessa differenza: 'modello scambio vuoto (T & a, T & b) {T tmp (std :: move (a)); a = std :: mossa (b); b = std :: mossa (TMP); } ' –

3

Poi l'espressione a = std::move(b); viene eseguito, l'oggetto è già vuoto, in uno stato in cui solo la distruzione è ben definita. Questo sarà effettivamente un no-op, poiché l'oggetto sul lato sinistro e destro è già vuoto.Lo stato dell'oggetto dopo la mossa è ancora sconosciuto ma distruttibile. La prossima istruzione sposta il contenuto da tmp e riporta l'oggetto a uno stato noto.

+1

In che modo l'oggetto vuoto risolve la contraddizione nella domanda? La citazione nella domanda non mi sembra consentire un'eccezione per gli oggetti vuoti. –

+0

@ BjarkeH.Roune: considera il codice: 'int i; i = i; i = 5; '. L'istruzione centrale legge una variabile non inizializzata, ma ciò non influisce sulla correttezza della dichiarazione o sull'assegnazione successiva. La citazione afferma che l'implementazione è autorizzata ad assumere che non ci sia aliasing (cioè 'this' e' rhs' siano oggetti diversi), che a sua volta significa che può rilasciare liberamente le risorse e poi prenderle dall'argomento, che sarebbe problematico nel caso generale, ma in questo caso la linea precedente ha già * preso * le risorse. –

+0

@ BjarkeH.Roune: [...] Si noti che la citazione non dice nemmeno che è un comportamento indefinito passare oggetti con alias, solo che l'implementazione può assumere liberamente che l'aliasing non si sia verificato.Nel momento in cui la seconda riga viene eseguita, i contenuti reali vengono mantenuti da qualche altra parte, quindi non vengono influenzati dall'espressione. Le garanzie sull'oggetto prima e dopo l'espressione sono esattamente le stesse e devono essere mantenute indipendentemente dal potenziale aliasing: l'oggetto deve essere distruttibile e questo è tutto ciò che è necessario per la terza istruzione. –

6

[res.on.arguments] è un'istruzione su come il client deve utilizzare std :: lib. Quando il client invia un xvalue a una funzione std :: lib, il client deve essere disposto a fingere che xvalue sia realmente un valore di prval, e si aspetta che std :: lib ne sfrutti.

Tuttavia, quando il client chiama std :: swap (x, x), il client non invia un xvalue a una funzione std :: lib. Invece è l'implementazione che sta facendo. E così l'onere è sull'implementazione per far funzionare std :: swap (x, x).

Detto questo, lo std ha dato al implementatore una garanzia: X deve soddisfare MoveAssignable. Anche se in uno stato spostato da, il client deve assicurarsi che X sia MoveAssignable. Inoltre, l'implementazione di std::swap non interessa molto a quale auto-spostamento-assegnazione, purché non sia un comportamento non definito per X. I.e. finché non si arresta in modo anomalo.

a = std::move(b); 

Quando & a == & b, sia l'origine e destinazione di questo compito hanno un valore specificato (spostato-da). Questo può essere un no-op o può fare qualcos'altro. Finché non si blocca, std :: swap funzionerà correttamente. Questo perché nella riga successiva:

b = std::move(tmp); 

qualsiasi valore è andato in a dalla riga precedente sta per essere dato un nuovo valore da tmp. E tmp ha il valore originale di a. Quindi oltre a bruciare un sacco di cicli cpu, swap(a, a) è un no-op.

Aggiorna

Il latest working draft, N4618 è stato modificato per indicare chiaramente che nei requisiti MoveAssignable l'espressione:

t = rv 

(dove rv è un rvalue), t deve essere solo il valore equivalente di rv prima dell'assegnazione se t e rv non fanno riferimento allo stesso oggetto. E a prescindere, lo stato di rv non è specificato dopo l'assegnazione. C'è una nota aggiuntiva per ulteriori chiarimenti:

rv deve ancora soddisfare i requisiti del componente libreria che lo sta utilizzando, anche se non t e rv si riferiscono allo stesso oggetto.

+0

Concordo sul fatto che l'implementatore della libreria controlli sia std :: swap che 'operator = (T &&)' in modo che possa fare ciò che vuole purché l'interfaccia esterna sia soddisfatta, quindi in tal senso la contraddizione viene risolta. Tuttavia, stai anche dicendo che per implementare 'std :: swap' nel modo mostrato, è necessario fare ipotesi sull'implementazione della libreria standard che non sono garantite dallo standard C++ 11, ma che può solo essere fatto in sicurezza dall'implementatore dell'intera biblioteca? O stai dicendo che MoveAssignable implica che 'v = std :: move (v)' deve funzionare, o almeno non crash? –

+0

'std :: vector' non deve essere 'MoveAssignable' per scambiare i vettori perché' swap' è specializzato per 'vettori'. Comunque sicuramente uno dovrebbe essere in grado di 'std :: sort' a' vector > 'e in questo caso' std :: sort' richiede che 'vector ' sia MoveAssignable, sia che sia stato spostato da o non. L'implementatore 'sort' è autorizzato ad auto-spostare-assegna questo' 'vector ' (o fa tutto il necessario per rallentare l'algoritmo). Tuttavia, non credo che l'implementatore 'sort' possa assumere quale sia il valore risultante di un'assegnazione auto-spostamento, o che sia un no-op. –

+0

Quindi * stai * dicendo che per un tipo che è MoveAssignable deve consentire l'assegnazione del movimento autonomo e quindi non può assumere il non-aliasing tra 'this' e il parametro nell'operatore di assegnazione del movimento? Ciò risolverebbe la contraddizione nella domanda. –

2

Sono d'accordo con la tua analisi, e infatti la libstdC++ Debug Mode ha un'affermazione che il fuoco sull'auto-swap di contenitori standard:

#include <vector> 
#include <utility> 

struct S { 
    std::vector<int> v; 
}; 

int main() 
{ 
    S s; 
    std::swap(s, s); 
} 

Il tipo di involucro S è necessario perché lo scambio direttamente vettore usa la specializzazione che chiama vector::swap() e quindi non usa il generico std::swap, ma S userà quello generico e quando compilato come C++ 11 che si tradurrà in un'assegnazione auto-spostamento del membro vettoriale, che abortirà :

/home/toor/gcc/4.8.2/include/c++/4.8.2/debug/vector:159:error: PST. 

Objects involved in the operation: 
sequence "this" @ 0x0x7fffe8fecc00 { 
    type = NSt7__debug6vectorIiSaIiEEE; 
} 
Aborted (core dumped) 

(Non so cosa "PST" dovrebbe significare lì! Penso che ci sia qualcosa di sbagliato nell'installazione con cui l'ho provato.)

Credo che il comportamento di GCC qui sia conforme, perché lo standard dice che l'implementazione può assumere che l'auto-spostamento-assegnazione non avviene mai, quindi l'affermazione non fallirà mai in un programma valido.

Tuttavia, sono d'accordo con Howard che questo ha bisogno di funzionare (e può essere fatto funzionare senza troppi problemi - per libstdC++ abbiamo solo bisogno di eliminare l'asserzione della modalità di debug!), E quindi dobbiamo correggere lo standard per fare un'eccezione per l'auto-spostamento, o almeno per l'auto-scambio. Mi è stato promesso di scrivere un articolo su questo argomento da tempo, ma non l'ho ancora fatto.

Credo che da quando ho scritto la sua risposta, Howard ora accetta che ci sia un problema con l'attuale formulazione nello standard, e abbiamo bisogno di correggerlo per impedire a libstdC++ di fare quella asserzione che fallisce.

Problemi correlati