13

C'è una funzione di conversione operator void*() const in classi di flusso C++. in modo che tutti gli oggetti stream possano essere convertiti implicitamente in void*. Durante l'interazione con i programmatori su SO mi suggeriscono di non utilizzare void* a meno che non si abbia una buona ragione per usarlo. void* è una tecnica di rimozione del controllo degli errori di tipo &. Quindi, a causa dell'esistenza di questa funzione di conversione seguente programma è perfettamente valido. Si tratta di un difetto nel C++ standard library.Perché la funzione di conversione void *() dell'operatore è stata aggiunta alle classi di flusso C++?

#include <iostream> 
int main() 
{ 
     delete std::cout; 
     delete std::cin; 
} 

Vedi demo live here.

Il programma di cui sopra è valido in C++ 03, ma non riesce a compilazione in C++ 11 & compilatori successivi, perché questa funzione di conversione è rimosso. Ma la domanda è perché faceva parte della libreria standard C++ se è pericolosa? Qual è lo scopo di consentire la conversione di oggetti flusso su void*? A che cosa serve?

+3

fwiw Penso che questi programmi non siano "validi", semplicemente capita di compilare. ma sicuramente danno un comportamento indefinito. –

+5

Questo potrebbe interessarti: http://www.artima.com/cppsource/safeboolP.html – moooeeeep

+0

@moooeeeep: sì, è sicuramente un post interessante. Grazie per il link, – Destructor

risposta

25

Una caratteristica di std::stringstream è che si prevede che se il flusso è usato come un bool, esso viene convertito in true se il flusso è ancora valido e false se non lo è. Ad esempio questo consente di utilizzare una sintassi semplice se si implementa la propria versione di cast lessicale.

(Per riferimento, boost contiene una funzione template chiamato lexical_cast che fa qualcosa di simile al seguente funzione template semplice.)

template <typename T, typename U> 
T lexical_cast(const U & u) { 
    T t; 
    std::stringstream ss; 
    if (ss << u && ss >> t) { 
     return t; 
    } else { 
     throw bad_lexical_cast(); 
    } 
} 

Se, per esempio, si lavora su un progetto in cui non sono consentite eccezioni, si potrebbe vuoi invece lanciare la seguente versione di questo.

template <typename T, typename U> 
boost::optional<T> lexical_cast(const U & u) { 
    T t; 
    std::stringstream ss; 
    if (ss << u && ss >> t) { 
     return t; 
    } else { 
     return boost::none; 
    } 
} 

(Ci sono vari modi sopra potrebbero essere migliorati, questo codice è solo per fare un esempio.)

Il operator void * viene utilizzato in quanto sopra come segue:

  1. ss << u restituisce un riferimento a ss.
  2. Il ss viene convertito in modo implicito in void * che è nullptr se l'operazione di flusso non è riuscita e non null se il flusso è ancora buono.
  3. L'operatore && si interrompe rapidamente, a seconda del valore di verità di quel puntatore.
  4. La seconda parte ss >> t viene eseguita e restituisce e viene anche convertita in void *.
  5. Se entrambe le operazioni sono riuscite, è possibile restituire il risultato in streaming t. Se uno dei due fallisce, segnaliamo un errore.

La funzione di conversione bool è fondamentalmente solo zucchero sintattico che consente di scrivere in modo conciso questa (e molte altre cose).

L'inconveniente di introdurre effettivamente una conversione bool implicita è che poi, std::stringstream diventa conversione implicita int e molti altri tipi anche perché è bool conversione implicita int. Questo alla fine provoca incubi sintattici altrove.

Per esempio, se std::stringstream avuto un operator bool conversione implicita, si supponga di avere questo semplice codice: (!)

std::stringstream ss; 
int x = 5; 
ss << x; 

Ora nella risoluzione di sovraccarico, si hanno due potenziali sovraccarichi da considerare, quella normale operator<<(std::stringstream &, int), e quello in cui ss viene convertito in bool, poi promosso int, e l'operatore bit di spostamento può applicare operator<<(int, int), tutto a causa della conversione implicita a bool ...

La soluzione alternativa consiste nell'utilizzare una conversione implicita in void *, che può essere utilizzata contestualmente come bool, ma non è in realtà implicitamente convertibile in bool o int.

In C++ 11 non abbiamo più bisogno di questa soluzione alternativa, e non c'è ragione per cui qualcuno avrebbe esplicitamente utilizzato la conversione void *, quindi è stata appena rimossa.

(Sto cercando un riferimento, ma credo che il valore di questa funzione sia stato specificato solo quando dovrebbe essere nullptr rispetto a quando non dovrebbe essere nullptr e che è stata definita l'implementazione di quel puntatore diverso da zero Valori potrebbe cedere. Quindi, qualsiasi codice che si basava sulla versione void * e non può essere banalmente riscritta per utilizzare la versione bool sarebbe stata corretta in ogni modo.)

il void * soluzione non è ancora senza problemi. In boost è stato sviluppato un linguaggio più sofisticato di "safe bool" per il codice pre-C++ 11, che è basato su qualcosa come T* dove T è un "tipo usato una volta", come una struttura definita nella classe implementare l'idioma, o usare una funzione pointer-to-member per quella classe e usare i valori di ritorno che sono o una particolare funzione di membro privato di quella classe, o nullptr. L'idioma "safe bool" è usato ad esempio in tutti gli indicatori intelligenti di boost.

Indipendentemente da questo intero problema è stato ripulito in C++ 11 introducendo explicit operator bool.

Maggiori informazioni qui: Is the safe-bool idiom obsolete in C++11?

+0

indietro: che cos'è t & u nel tuo codice? – Destructor

+0

Qualcosa è deprecato * o * rimosso, non entrambi. La deprecazione significa che l'utilizzo è scoraggiato e probabilmente la rimozione avverrà in futuro. –

+3

C++ 03 non ha usato l'idioma "safe bool". Invece ha usato un operatore di conversione per 'void *'. L'idioma bool sicuro era un concetto aggiunto per aumentare perché mentre una conversione a 'bool' non era sicura, così è la conversione in' void * '. –

11

Questo è un difetto nella libreria standard C++.

Questo era un difetto nelle versioni precedenti (1998 e 2003) della libreria standard C++. Questo difetto non esiste più nel 2011 e nelle versioni successive dello standard. Una nuova funzionalità, la possibilità di contrassegnare gli operatori di conversione come explicit, è stata aggiunta alla lingua per rendere sicuro l'operatore di conversione su bool.

Gli sviluppatori della versione originale dello standard C++ esplicitamente scelto di utilizzare una conversione a void* piuttosto che una conversione a bool perché una conversione non esplicita a bool era abbastanza pericoloso in un certo numero di modi.In confronto, mentre lo operator void*() era piuttosto ovviamente kludgy, ha funzionato, almeno finché non hai lanciato quel puntatore su qualcos'altro o non provi ad eliminarlo. (In alternativa, operator!, è stato probabilmente più sicuro di uno di questi operatori di conversione, ma che avrebbe richiesto la non-intuitivo e astrusa while (!!stream) {...}.)

Il concetto del linguaggio "sicuro bool" è stato sviluppato dopo l'originale 1998/È stata rilasciata la versione 1999 dello standard. Se è stato sviluppato prima del 2003 è un po 'irrilevante; la versione 2003 dello standard era intesa come correzione di un bug allo standard originale. Che operator void*() let delete std::cin compile non è stato considerato un errore tanto quanto un "non fare quello quindi" tipo di problema.

Lo sviluppo dell'idioma "safe bool" ha dimostrato che esistevano alternative che rendevano sicuro operator bool(), ma se si osservano alcune delle implementazioni, sono tutte molto complesse e massicciamente sfacciate. La soluzione C++ 11 era incredibilmente semplice: consentire agli operatori di conversione di essere qualificati con la parola chiave explicit. La soluzione C++ 11 ha rimosso l'operatore di conversione void* e ha aggiunto un operatore di conversione explicit bool. Questo ha reso l'idioma "safe bool" obsoleto, almeno finché si utilizza un compilatore conforme a C++ 11 (o successivo).

+0

'delete std :: cin;' - Anche se potrebbe essere compilato, 'delete'ing un puntatore' void * 'è comunque un comportamento indefinito. – 5gon12eder

Problemi correlati