2013-07-03 6 views
5

Dopo aver effettuato il passaggio a C++ 11, ora sto sistematicamente passando le mie stringhe di valore nei miei costruttori. Ma ora, mi rendo conto che lo rende più facile da introdurre bug quando anche utilizzando il valore nel corpo del costruttore:Utilizzo errato di un valore spostato

class A(std::string val): 
    _val(std::move(val)) 
{ 
    std::cout << val << std::endl; // Bug!!! 
} 

Cosa posso fare per ridurre le probabilità di sbagliare?

+7

"fatto la _move_ a C++ 11" molto bello –

+0

"Cosa posso fare per ridurre le possibilità di sbagliare?" Non tanto. Lasciare oggetti in stati validi ma strani è ridicolo, ed è una delle ragioni per cui la semantica del movimento di C++ 11 è stupida. –

+0

Cosa c'è di sbagliato nell'usare _val all'interno del corpo del costruttore? – Thorsten

risposta

2

argomenti Nome il cui scopo è quello di essere spostato-da in qualche modo distintivo, almeno nella attuazione del costruttore

A::A(std::string val_moved_from): 
_val(std::move(val_moved_from)) 
{ 
    std::cout << val_moved_from << std::endl; // Bug, but obvious 
} 

poi passare da loro il più presto possibile (nell'elenco costruzione, dicono) .

Se si dispone di un elenco di costruzione così lungo è possibile perdere due usi di val_moved_from in esso, questo non aiuta.

Un'alternativa sarebbe scrivere una proposta per risolvere questo problema. Supponi, estendi C++ in modo che i tipi o gli ambiti delle variabili locali possano essere modificati dalle operazioni su di essi, quindi std::safe_move(X) si sposta entrambi da X e contrassegna X come variabile scaduta, non più valida da utilizzare, per il resto del suo ambito. Elaborare cosa fare quando una variabile è scaduta per metà (scaduta in un ramo, ma non in un altro) è una domanda interessante.

Dato che è folle, possiamo invece attaccarlo come un problema di libreria. In una certa misura, possiamo simulare questo tipo di trucchi (una variabile il cui tipo cambia) in fase di esecuzione. Si tratta di greggio, ma dà l'idea:

template<typename T> 
struct read_once : std::tr2::optional<T> { 
    template<typename U, typename=typename std::enable_if<std::is_convertible<U&&, T>::value>::type> 
    read_once(U&& u):std::tr2::optional<T>(std::forward<U>(u)) {} 
    T move() && { 
    Assert(*this); 
    T retval = std::move(**this); 
    *this = std::tr2::none_t; 
    return retval; 
    } 
    // block operator*? 
}; 

cioè, scrivere tipo lineare che può essere letto solo da via move, e dopo che il tempo leggendo Assert s o tiri.

Quindi modificare il vostro costruttore:

A::A(read_once<std::string> val): 
    _val(val.move()) 
{ 
    std::cout << val << std::endl; // does not compile 
    std::cout << val.move() << std::endl; // compiles, but asserts or throws 
} 

con costruttori di inoltro, è possibile esporre un'interfaccia meno ridicola senza read_once tipi, poi in avanti i tuoi costruttori al "sicuro" (possibilmente private) versioni con read_once<> wrapper intorno gli argomenti.

Se i test coprono tutte le percorsi di codice, si otterrà bel Assert s invece di vuote std::string s, anche se si va e più di una volta move dai vostri read_once variabili.

+2

Ma devi ricordarti di fare tutto questo. Se riesci a ricordartelo, probabilmente non avresti commesso l'errore in primo luogo. –

+0

@LightnessRacesinOrbit Sure: ma se tu avessi un robusto 'read_once', e segui la regola" prendi sempre 'di valore per essere copiato' come 'read_once'", riceverai 'Assert' invece di dati vuoti quando lo leggi . E il fischio toglie le tigri, vedi tigri? – Yakk

+0

E se hai seguito la regola "non provare a stampare la variabile sbagliata" allora sarebbe tutto da contrattare –

0

"Avendo fatto il passaggio a C++ 11, ora sto sistematicamente passando le mie stringhe di valore nei miei costruttori."

Mi dispiace, non vedo perché uno vorrebbe farlo. Quale miglioramento fornisce in relazione al metodo tradizionale? (che è fondamentalmente a prova di bug).

 
class A(const std::string & s): 
    _val(s) 
{ 
    std::cout << s << std::endl; // no Bug!!! 
    std::cout << _val << std::endl; // no Bug either !!! 
} 

+0

Per rispondere alla tua domanda: sì, 'std :: move' è meglio di così perché, beh, meno copie. Per di più, la tua risposta non risponde alla domanda. – milleniumbug

+0

Supponiamo che 's' sia una stringa di 100.000 caratteri. La tua versione ne fa sempre una copia, sempre, proprio qui: '_val (s)'. La versione sopra non ne farà una copia se il chiamante fa 'std :: move' nel costruttore, o costruisce' A' da un 'std :: string temporaneo. Vedi [qui] (http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/) per una discussione più approfondita. Nota che avere un costruttore 'std :: string && s' e' std :: string const & s' ti dà 1 mossa in meno di prendere 'std :: string s', ma raddoppia la quantità di codice che devi mantenere. – Yakk

+0

In questo caso non stiamo facendo nessuna copia mentre passiamo per riferimento. Sono molto familiare con la discussione citata. Affronta una situazione in cui ci sono copie che non è il caso qui. La domanda posta è "Cosa posso fare per ridurre le possibilità di sbagliare?". La risposta è "Non farlo!". In questo modo non stai riducendo le copie e stai introducendo un bug. –