2013-06-25 11 views
5

Ho creato un tipo di flusso personalizzato, chiamandolo error_stream, che deriva da std::ostringstream. Ho anche creato un manipolatore personalizzato per lo stream chiamato throw_cpp_class (throw_cpp è un'istanza di throw_cpp_class). Il mio obiettivo era quello di avere questa sintassi:È ragionevolmente efficiente inserirli in un riferimento di rvalue a un flusso?

error_stream s; 
s << "some error " << message() << throw_cpp; // throw_cpp throws an exception containing contents of the stream. 

ho scoperto che attraverso la definizione di un operatore di inserimento che prende un riferimento rvalue al flusso come primo operando, ora posso fare questo:

error_stream() << "some error " << message() << throw_cpp; 

Il L'operatore di inserimento è simile al seguente:

error_stream& operator<<(error_stream&& s, const throw_cpp_class&) 
{ 
    throw s.str(); 
    return s; 
} 

Cosa sta succedendo qui? Perché è possibile restituire un valore di tipo error_stream&& in cui è richiesto un error_stream&? (Questo richiama il costruttore di mosse?). È orribilmente inefficiente? (Non che mi importi davvero, dato che l'eccezione dovrebbe essere rara).

+0

Questo è in realtà un po 'pulito ... Penso che potrei preferire 'throw_cpp ' però, per lanciare quell'errore con il contenuto dello stream. –

+1

Rilascerei il tipo restituito e l'istruzione 'return os'. È un errore semantico scrivere 's <<" qualche errore "<< messaggio() << throw_cpp <<" more ";'. Restituendo 'void', diventa anche un errore di sintassi e il compilatore lo prenderà. – MSalters

+0

@MSalters - buon punto. Ho fatto il cambiamento. – 0xbe5077ed

risposta

15

Con questo codice:

error_stream& operator<<(error_stream&& s, const throw_cpp_class&) 
{ 
    throw s.str(); 
    return s; 
} 

Potete restituire error_stream&& s come error_stream&, perché s è un lvalue, e non è un rvalue.

"Cosa?" tu chiedi? "Ma vedo && proprio lì!". Questa parte del C++ è complicata. Quando vedi type&& s (e type non è un modello), significa che la variabile è un riferimento di rvalue, che è un riferimento "costruito da" un valore. Ma ha un nome: s. E tutto con un nome è un lvalue. Questo è perché devi chiamare il numero std::move a volte, perché devi far sapere al compilatore che vuoi trattare di nuovo quella variabile come un valore.

Questo richiama il costruttore di spostamenti?).

No, restituisce semplicemente un riferimento al valore di lv s.

È orribilmente inefficiente? (Non che mi importi davvero, dato che l'eccezione dovrebbe essere rara).

No, dal momento che non c'è nessuna copia né una mossa.


non collegati alla tua domanda attuale, la maggior parte dei sovraccarichi per i flussi sono:

ostream& operator<<(ostream&& s, const T&) 

allora significa che a meno che throw_cpp è la prima cosa streaming, il vostro sovraccarico non sarà chiamato, perché la cosa precedente streaming volontà restituire un ostream&, non uno error_stream&&. (Nota: dovrebbero essere modelli, ma molti non lo sono, ed è irrilevante al punto) Dovrai restituirlo a un error_stream.

Inoltre, non è così che funzionano i manipolatori. I manipolatori sono funzioni, e quando lo streaming quelle funzioni a un ruscello, torrente chiama la funzione e passa stesso come parametro, in modo che ci si vuole qualcosa di più in questo modo:

template <class exception, class charT, class traits> 
std::basic_ostream<charT,traits>& throw_cpp(std::basic_ostream<charT,traits>& os) 
{ 
    error_stream& self = dynamic_cast<error_stream&>(os); //maybe throws std::bad_cast 
    throw exception(self.str()); 
    return os; //redundant, but the compiler might not know that. 
} 

Here it is at work (with stringstream)

+0

Questa è la versione * corta *? Non mi piacerebbe vedere la versione lunga ... –

+0

HA! Buon punto Questo era fondamentalmente solo un collegamento con l'altra risposta. Ho rimosso quella frase ora che questa risposta è in realtà _longer_ rispetto al linked-to. –

3

T&& è un rvalue riferimento, ma la sua categoria valore è quella di riferimento (valore assegnabile) in modo che possono essere immagazzinate all'interno lvalue riferimento. Anche in questo caso non vengono chiamati costruttori move/copy, perché vengono presi/restituiti per riferimento.

Non direi che questo è inefficiente nel minimo, ma piuttosto un uso comune e idiomatico dei riferimenti di valore.

Problemi correlati