Chi ha progettato / progettato IOStreams di C ++ e sarebbe ancora considerato ben progettato dagli standard odierni? [chiuso]


127

Prima di tutto, può sembrare che sto chiedendo opinioni soggettive, ma non è quello che sto cercando. Mi piacerebbe ascoltare alcuni argomenti ben fondati su questo argomento.


Nella speranza di avere un'idea di come dovrebbe essere progettato un moderno framework di streaming / serializzazione, di recente mi sono procurato una copia del libro Standard C ++ IOStreams and Locales di Angelika Langer e Klaus Kreft . Ho pensato che se IOStreams non fosse stato ben progettato, in primo luogo non sarebbe arrivato alla libreria standard C ++.

Dopo aver letto varie parti di questo libro, sto iniziando a dubitare che IOStreams possa essere paragonato, ad esempio, alla STL da un punto di vista architettonico complessivo. Leggi ad esempio questa intervista con Alexander Stepanov (l '"inventore" della STL) per conoscere alcune decisioni di progettazione che sono entrate nella STL.

Cosa mi sorprende in particolare :

  • Sembra sconosciuto chi sia stato responsabile del design complessivo di IOStreams (mi piacerebbe leggere alcune informazioni di base su questo - qualcuno conosce buone risorse?);

  • Dopo aver approfondito la superficie immediata di IOStreams, ad esempio se si desidera estendere IOStreams con le proprie classi, si arriva a un'interfaccia con nomi di funzioni membro abbastanza criptici e confusi, ad esempio getloc/ imbue, uflow/ underflow, snextc/ sbumpc/ sgetc/ sgetn, pbase/ pptr/ epptr(e c'è esempi probabilmente anche peggiori). Ciò rende molto più difficile comprendere il design generale e il modo in cui le singole parti cooperano. Anche il libro che ho citato sopra non aiuta che molto (IMHO).


Quindi la mia domanda:

Se si dovesse giudicare per gli standard di ingegneria del software di oggi (se in realtà non v'è alcun accordo generale su questi), sarebbe s 'C ++ iostreams ancora essere considerato ben progettato? (Non vorrei migliorare le mie capacità di progettazione del software da qualcosa che è generalmente considerato obsoleto.)


7
Interessante opinione di Herb Sutter stackoverflow.com/questions/2485963/… :) Peccato che quel ragazzo abbia lasciato SO dopo solo pochi giorni di partecipazione
Johannes Schaub - litb

5
C'è qualcun altro che vede una mescolanza di preoccupazioni nei flussi STL? Un flusso è normalmente progettato per leggere o scrivere byte e nient'altro. Una cosa che può leggere o scrivere tipi di dati specifici è un formattatore (che potrebbe non essere necessario utilizzare un flusso per leggere / scrivere i byte formattati). Mescolare entrambi in una classe rende ancora più complesso implementare i propri flussi.
mmmmmmmm,

4
@rsteven, c'è una separazione di queste preoccupazioni. std::streambufè la classe base per la lettura e la scrittura di byte e istream/ ostreamè per input e output formattati, prendendo un puntatore a std::streambufcome destinazione / sorgente.
Johannes Schaub - litb

1
@litb: Ma è possibile cambiare lo streambuf utilizzato dallo stream (formatter)? Quindi forse voglio usare la formattazione STL ma voglio scrivere i dati tramite uno streambuf specifico?
mmmmmmmm,

2
@rstevens,ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Johannes Schaub - litb

Risposte:


31

Diverse idee mal concepite hanno trovato la loro strada nello standard: auto_ptr, vector<bool>, valarraye export, solo per citarne alcuni. Quindi non prenderei la presenza di IOStreams necessariamente come un segno di design di qualità.

IOStreams hanno una storia a scacchi. Sono in realtà una rielaborazione di una precedente libreria di flussi, ma sono stati creati in un momento in cui molti dei modi di dire C ++ di oggi non esistevano, quindi i progettisti non hanno avuto il senno di poi. Un problema che è diventato evidente solo nel tempo è che è quasi impossibile implementare IOStreams con la stessa efficienza dello stdio di C, a causa dell'uso copioso di funzioni virtuali e dell'inoltro a oggetti buffer interni anche con la massima granularità, e anche grazie a una stranezza imperscrutabile nel modo in cui i locali sono definiti e implementati. Il mio ricordo di questo è piuttosto confuso, lo ammetto; Ricordo di essere stato oggetto di un intenso dibattito alcuni anni fa, su comp.lang.c ++. Moderato.


3
Grazie per il tuo contributo. Esplorerò l' comp.lang.c++.moderatedarchivio e pubblicherò i collegamenti in fondo alla mia domanda se trovo qualcosa di prezioso. - Inoltre, oso non essere d'accordo con te su auto_ptr: Dopo aver letto il C ++ eccezionale di Herb Sutter sembra una classe molto utile quando si implementa il modello RAII.
stakx - non contribuisce più

5
@stakx: Ciononostante viene deprecato e sostituito da unique_ptruna semantica più chiara e più potente.
UncleBens

3
@UncleBens unique_ptrrichiede un riferimento al valore. Quindi a questo punto auto_ptrè un puntatore molto potente.
Artyom,

7
Ma auto_ptrha rovinato la semantica della copia / assegnazione che la rende una nicchia per i bug di dereferenziazione ...
Matthieu M.

5
@TokenMacGuy: non è un vettore e non memorizza bool. Il che lo rende in qualche modo fuorviante. ;)
jalf

40

Per quanto riguarda chi li ha progettati, la biblioteca originale è stata (non a caso) creata da Bjarne Stroustrup, e quindi reimplementata da Dave Presotto. Questo è stato quindi riprogettato e reimplementato ancora una volta da Jerry Schwarz per Cfront 2.0, usando l'idea dei manipolatori di Andrew Koenig. La versione standard della libreria si basa su questa implementazione.

Fonte "The Design & Evolution of C ++", sezione 8.3.1.


3
@Neil - nut qual è la tua opinione sul design? Sulla base delle tue altre risposte, molte persone vorrebbero sentire la tua opinione ...
DVK,

1
@DVK Ho appena pubblicato la mia opinione come risposta separata.

2
Ho appena trovato una trascrizione di un'intervista a Bjarne Stroustrup in cui menziona alcuni frammenti della storia di IOStreams: www2.research.att.com/~bs/01chinese.html (questo link sembra essere temporaneamente interrotto in questo momento, ma puoi provare Cache della pagina di Google)
stakx - che non contribuisce più

2

28

Se dovessi giudicare in base agli standard di ingegneria del software di oggi (se esiste effettivamente un accordo generale su questi), gli IOStreams di C ++ sarebbero ancora considerati ben progettati? (Non vorrei migliorare le mie capacità di progettazione del software da qualcosa che è generalmente considerato obsoleto.)

Direi NO , per diversi motivi:

Gestione degli errori scadente

Le condizioni di errore devono essere segnalate con eccezioni, non con operator void*.

L'anti-pattern "oggetto zombi" è ciò che causa bug come questi .

Scarsa separazione tra formattazione e I / O

Ciò rende gli oggetti stream inutili e complessi, poiché devono contenere ulteriori informazioni sullo stato per la formattazione, indipendentemente dal fatto che siano necessari o meno.

Aumenta anche le probabilità di scrivere bug come:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

Se invece hai scritto qualcosa del tipo:

cout << pad(to_hex(x), 8, '0') << endl;

Non ci sarebbero bit di stato relativi alla formattazione e nessun problema.

Si noti che in linguaggi "moderni" come Java, C # e Python, tutti gli oggetti hanno una funzione toString/ ToString/ __str__che viene chiamata dalle routine di I / O. AFAIK, solo C ++ fa il contrario usando stringstreamcome metodo standard di conversione in una stringa.

Scarso supporto per i18n

L'output basato su Iostream divide i letterali di stringa in pezzi.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Le stringhe di formato mettono intere frasi in valori letterali di stringa.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

Quest'ultimo approccio è più facile da adattare alle librerie di internazionalizzazione come GNU gettext, perché l'uso di intere frasi fornisce più contesto ai traduttori. Se la routine di formattazione delle stringhe supporta il riordino (come i $parametri POSIX printf), gestisce anche meglio le differenze nell'ordine delle parole tra le lingue.


4
In realtà, per i18n, le sostituzioni dovrebbero essere identificate dalle posizioni (% 1,% 2, ..), poiché una traduzione potrebbe richiedere la modifica dell'ordine dei parametri. Altrimenti, sono pienamente d'accordo - +1.
Peter

4
@peterchen: è quello che il POSIX $specificazioni per printfsono.
jamesdlin,

2
Il problema non sono le stringhe di formato, è che C ++ ha varargs non tipicamente sicuri.
dan04,

5
A partire da C ++ 11 ora ha varargs typesafe.
Mooing Duck il

2
IMHO le "informazioni supplementari sullo stato" sono il problema peggiore. cout è un globale; il collegamento di flag di formattazione rende globali tali flag e, se si considera che la maggior parte degli utilizzi hanno un ambito previsto di poche righe, è piuttosto terribile. Sarebbe possibile risolverlo con una classe di "formatter", che si lega a un ostream ma mantiene il suo stato. E, le cose fatte con cout sembrano generalmente terribili rispetto alla stessa cosa fatta con printf (quando ciò è possibile) ..
Greggo

17

Sto postando questo come una risposta separata perché è pura opinione.

Eseguire input e output (in particolare input) è un problema molto, molto difficile, quindi non sorprende che la libreria iostreams sia piena di cose e cose che con il senno di poi perfetto avrebbero potuto essere fatte meglio. Ma mi sembra che tutte le librerie di I / O, in qualunque lingua siano così. Non ho mai usato un linguaggio di programmazione in cui il sistema I / O era una cosa di bellezza che mi ha fatto reverire il suo progettista. La libreria iostreams presenta dei vantaggi, in particolare rispetto alla libreria CI / O (estensibilità, sicurezza dei tipi, ecc.), Ma non credo che nessuno la stia prendendo in considerazione come esempio di OO eccezionale o design generico.


16

La mia opinione sugli iostreams in C ++ è migliorata sostanzialmente nel tempo, in particolare dopo che ho iniziato a estenderli implementando le mie classi di stream. Ho iniziato ad apprezzare l'estensibilità e il design generale, nonostante i nomi delle funzioni dei membri ridicolmente scadenti come xsputno altro. Indipendentemente da ciò, penso che i flussi di I / O rappresentino un enorme miglioramento rispetto a C stdio.h, che non ha alcuna sicurezza di tipo ed è pieno di gravi difetti di sicurezza.

Penso che il problema principale con i flussi di IO sia che confondono due concetti correlati ma in qualche modo ortogonali: formattazione testuale e serializzazione. Da un lato, i flussi IO sono progettati per produrre una rappresentazione testuale formattata e leggibile dall'uomo di un oggetto e, dall'altro, per serializzare un oggetto in un formato portatile. A volte questi due obiettivi sono la stessa cosa, ma altre volte ciò comporta alcune incongruenze seriamente fastidiose. Per esempio:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Qui, ciò che otteniamo come input non è quello che abbiamo originariamente trasmesso allo stream. Questo perché l' <<operatore emette l'intera stringa, mentre l' >>operatore leggerà dallo stream solo fino a quando non incontra un carattere di spazio bianco, poiché non ci sono informazioni sulla lunghezza memorizzate nello stream. Quindi anche se produciamo un oggetto stringa contenente "ciao mondo", inseriremo solo un oggetto stringa contenente "ciao". Pertanto, sebbene lo stream abbia funzionato come una funzione di formattazione, non è riuscito a serializzare correttamente e quindi a annullare la serializzazione dell'oggetto.

Si potrebbe dire che i flussi IO non sono stati progettati per essere strutture di serializzazione, ma in tal caso, a cosa servono realmente i flussi di input ? Inoltre, in pratica i flussi di I / O vengono spesso utilizzati per serializzare gli oggetti, poiché non esistono altre funzionalità di serializzazione standard. Considera boost::date_timeo boost::numeric::ublas::matrix, dove se si genera un oggetto matrice con l' <<operatore, otterrai la stessa matrice esatta quando lo inserisci usando l' >>operatore. Ma per raggiungere questo obiettivo, i progettisti di Boost hanno dovuto archiviare le informazioni sul conteggio delle colonne e sul conteggio delle righe come dati testuali nell'output, il che compromette l'effettiva visualizzazione leggibile dall'uomo. Ancora una volta, una combinazione imbarazzante di funzionalità di formattazione testuale e serializzazione.

Nota come la maggior parte delle altre lingue separa queste due strutture. In Java, ad esempio, la formattazione viene eseguita tramite il toString()metodo, mentre la serializzazione viene eseguita tramite l' Serializableinterfaccia.

Secondo me, la soluzione migliore sarebbe stata quella di introdurre flussi basati su byte , insieme ai flussi basati su caratteri standard . Questi flussi funzionerebbero su dati binari, senza preoccupazioni per la formattazione / visualizzazione leggibili dall'uomo. Potrebbero essere utilizzati esclusivamente come strutture di serializzazione / deserializzazione, per tradurre oggetti C ++ in sequenze di byte portatili.


grazie per aver risposto. Potrei sbagliarmi al riguardo, ma per quanto riguarda il tuo ultimo punto (flussi basati su byte o basati su caratteri), la risposta di IOStream (parziale?) A questa non è la separazione tra buffer di flusso (conversione dei caratteri, trasporto e buffering) e stream (formattazione / analisi)? E non potresti creare nuove classi di stream, quelle pensate esclusivamente per la serializzazione e la deserializzazione (leggibili da una macchina) e altre che sono unicamente orientate alla formattazione e all'analisi (leggibili dall'uomo)?
stakx - non contribuisce più al

@stakx, sì, e in effetti l'ho fatto. È leggermente più fastidioso di quanto sembri, dal momento che std::char_traitsnon può essere portabilmente specializzato per prendere un unsigned char. Tuttavia, ci sono soluzioni alternative, quindi immagino che l'estensibilità venga di nuovo in soccorso. Ma penso che il fatto che i flussi basati su byte non siano standard sia un punto debole della libreria.
Charles Salvia,

4
Inoltre, l'implementazione di flussi binari richiede l'implementazione di nuove classi di flusso e nuove classi di buffer, poiché i problemi di formattazione non sono completamente separati std::streambuf. Quindi, sostanzialmente l'unica cosa che stai estendendo è la std::basic_iosclasse. Quindi c'è una linea in cui "l'estensione" passa attraverso il territorio "completamente reimplementazione" e la creazione di un flusso binario dalle strutture di flusso I / O C ++ sembra avvicinarsi a quel punto.
Charles Salvia,

ben detto ed esattamente quello che sospettavo. E il fatto che sia il C sia il C ++ facciano di tutto per non dare garanzie su specifiche larghezze di bit e rappresentazioni può effettivamente diventare problematico quando si tratta di fare I / O.
stakx - non contribuisce più il

" serializzare un oggetto in un formato portatile. " no, non erano mai pensati per supportarlo
curiousguy,

11

ho sempre trovato IOStreams C ++ mal progettati: la loro implementazione rende molto difficile definire correttamente un nuovo tipo di flusso. mischiano anche funzioni io e funzionalità di formattazione (pensate ai manipolatori).

personalmente, la migliore progettazione e implementazione del flusso che abbia mai trovato risiede nel linguaggio di programmazione Ada. è un modello di disaccoppiamento, una gioia creare un nuovo tipo di flussi e le funzioni di output funzionano sempre indipendentemente dal flusso utilizzato. questo grazie a un minimo comune denominatore: hai emesso byte in un flusso e basta. le funzioni stream si occupano di mettere i byte nello stream, non è loro compito ad esempio formattare un numero intero in esadecimale (ovviamente, esiste una serie di attributi di tipo, equivalenti a un membro della classe, definiti per la gestione della formattazione)

Vorrei che C ++ fosse semplice per quanto riguarda i flussi ...


Il libro che ho citato spiega l'architettura di base di IOStreams come segue: Esiste un livello di trasporto (le classi del buffer di flusso) e un livello di analisi / formattazione (le classi del flusso). I primi sono responsabili della lettura / scrittura dei caratteri da / verso un bytestream, mentre i secondi sono responsabili dell'analisi dei caratteri o della serializzazione dei valori in caratteri. Questo sembra abbastanza chiaro, ma non sono sicuro che queste preoccupazioni siano veramente chiaramente separate nella realtà, esp. quando entrano in gioco i locali. - Concordo anche con te sulla difficoltà di implementare nuove classi di stream.
stakx - non contribuisce più

"Mix io ​​features and formatting features" <- Cosa c'è che non va? Questo è un po 'il punto della biblioteca. Per quanto riguarda la creazione di nuovi flussi, è necessario creare uno streambuf anziché uno stream e costruire un flusso semplice attorno allo streambuf.
Billy ONeal,

sembra che le risposte a questa domanda mi abbiano fatto capire qualcosa che non mi è mai stato spiegato: dovrei derivare uno streambuf anziché uno stream ...
Adrien Plisson

@stakx: se il layer streambuf ha fatto quello che hai detto, andrebbe bene. Ma la conversione tra sequenza di caratteri e byte sono tutti confusi con l'attuale I / O (file, console, ecc.). Non è possibile eseguire l'I / O dei file senza eseguire anche la conversione dei caratteri, il che è molto sfortunato.
Ben Voigt,

10

Penso che il design di IOStreams sia geniale in termini di estensibilità e utilità.

  1. Buffer di streaming: dai un'occhiata alle estensioni boost.iostream: crea gzip, tee, copia stream in poche righe, crea filtri speciali e così via. Non sarebbe possibile senza di essa.
  2. Integrazione della localizzazione e integrazione della formattazione. Guarda cosa si può fare:

    std::cout << as::spellout << 100 << std::endl;

    Può stampare: "cento" o anche:

    std::cout << translate("Good morning")  << std::endl;

    È possibile stampare "Bonjour" o "בוקר טוב" in base alla lingua locale std::cout!

    Queste cose possono essere fatte solo perché gli iostreams sono molto flessibili.

Potrebbe essere fatto meglio?

Certo che potrebbe! In effetti ci sono molte cose che potrebbero essere migliorate ...

Oggi è abbastanza doloroso derivare correttamente stream_buffer, è abbastanza banale aggiungere ulteriori informazioni di formattazione allo streaming, ma possibile.

Ma guardando indietro di molti anni fa, il design della biblioteca era ancora abbastanza buono da essere in procinto di portare molte chicche.

Perché non puoi sempre vedere il quadro generale, ma se lasci punti per le estensioni ti dà abilità molto migliori anche in punti a cui non hai pensato.


5
Puoi fornire un commento sul perché i tuoi esempi per il punto 2 sarebbero migliori del semplice utilizzo di qualcosa del genere print (spellout(100));e print (translate("Good morning"));questa sembrerebbe una buona idea, poiché questo disaccoppia la formattazione e i18n dall'I / O.
Schedler

3
Perché può essere tradotto secondo un linguaggio imbevuto di corrente. cioè french_output << translate("Good morning"):; english_output << translate("Good morning") ti darei: "Buon giorno Bonjour"
Artyom,

3
La localizzazione è molto più difficile quando è necessario eseguire '<< "testo" << valore' in una lingua ma '<< valore << "testo"' in un'altra - rispetto a printf
Martin Beckett,

@Martin Beckett Lo so, dai un'occhiata alla libreria Boost.Locale, cosa succede che in tal caso lo fai out << format("text {1}") % valuee può essere tradotto in "{1} translated". Quindi funziona benissimo ;-).
Artyom,

15
Ciò che "si può fare" non è molto rilevante. Sei un programmatore, tutto può essere fatto con sufficiente sforzo. Ma IOStreams rende terribilmente doloroso raggiungere la maggior parte di ciò che può essere fatto . E di solito ottieni prestazioni scadenti per i tuoi problemi.
jalf

2

(Questa risposta si basa solo sulla mia opinione)

Penso che gli IOStreams siano molto più complessi delle loro equivalenti di funzione. Quando scrivo in C ++, uso ancora le intestazioni di cstdio per l'I / O "vecchio stile", che trovo molto più prevedibile. Da un lato, (anche se non è molto importante, la differenza di tempo assoluta è trascurabile), in numerose occasioni è stato dimostrato che i flussi IOS sono più lenti di CI / O.


Penso che intendi "funzione" piuttosto che "funzionale". la programmazione funzionale produce codice che sembra ancora peggio di una programmazione generica.
Chris Becke,

Grazie per aver segnalato quell'errore; Ho modificato la risposta per riflettere la correzione.
Delan Azabani,

5
Gli IOS stream dovrebbero quasi certamente essere più lenti dello stdio classico; se mi venisse assegnato il compito di progettare un framework di flussi I / O estensibili e facili da usare, probabilmente giudicherei la velocità secondaria, dato che i veri colli di bottiglia saranno probabilmente la velocità I / O dei file o la larghezza di banda del traffico di rete.
stakx - non contribuisce più

1
Concordo sul fatto che per l'I / O o la rete la velocità computazionale non conta molto. Tuttavia, ricorda che sta usando il C ++ per la conversione numerica / stringa sstringstream. Penso che la velocità sia importante, sebbene sia secondaria.
Matthieu M.,

1
L'I / O dei file @stakx e i colli di bottiglia della rete sono una funzione dei costi "per byte" che sono piuttosto piccoli e ridotti drasticamente dai miglioramenti della tecnologia. Inoltre, dato DMA, questi overhead non tolgono il tempo di CPU da altri thread sulla stessa macchina. Quindi, se si esegue un output formattato, il costo per farlo in modo efficiente rispetto a no, può facilmente essere significativo (almeno, non oscurato dal disco o dalla rete; più probabilmente è oscurato da altre elaborazioni nell'app).
Greggo,

2

Mi imbatto sempre in sorprese quando utilizzo IOStream.

La libreria sembra orientata al testo e non binaria. Questa potrebbe essere la prima sorpresa: l'uso del flag binario nei flussi di file non è sufficiente per ottenere un comportamento binario. L'utente Charles Salvia sopra lo ha osservato correttamente: IOStreams mescola gli aspetti di formattazione (dove si desidera un output grazioso, ad esempio cifre limitate per i float) con aspetti di serializzazione (dove non si desidera la perdita di informazioni). Probabilmente sarebbe bene separare questi aspetti. Boost.Serialization fa questa metà. Hai una funzione di serializzazione che instrada verso gli inseritori e gli estrattori, se lo desideri. Lì già hai la tensione tra entrambi gli aspetti.

Molte funzioni hanno anche una semantica confusa (ad esempio get, getline, ignore e read. Alcuni estraggono il delimitatore, altri no; anche alcuni ne impostano eof). Inoltre, alcuni citano i nomi delle strane funzioni durante l'implementazione di uno stream (ad esempio xsputn, uflow, underflow). Le cose peggiorano ancora quando si usano le varianti di wchar_t. Il wifstream fa una traduzione in multibyte mentre wstringstream no. L'I / O binario non funziona immediatamente con wchar_t: hai la sovrascrittura del codecvt.

L'I / O con buffer c (cioè FILE) non è potente come la sua controparte C ++, ma è più trasparente e ha un comportamento molto meno intuitivo.

Ancora ogni volta che inciampo su IOStream, sono attratto da esso come una falena al fuoco. Probabilmente sarebbe una buona cosa se un ragazzo davvero intelligente guardasse bene l'architettura generale.


1

Non posso fare a meno di rispondere alla prima parte della domanda (chi l'ha fatto?). Ma è stato risposto in altri post.

Per quanto riguarda la seconda parte della domanda (ben progettata?), La mia risposta è un clamoroso "No!". Ecco un piccolo esempio che mi fa scuotere la testa incredulo da anni:

#include <stdint.h>
#include <iostream>
#include <vector>

// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
    std::vector<_T>::const_iterator iter;
    std::cout << title << " (" << v.size() << " elements): ";
    for( iter = v.begin(); iter != v.end(); ++iter )
    {
        std::cout << (*iter) << " ";
    }
    std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
    std::vector<uint8_t> byteVector;
    std::vector<uint16_t> wordVector;
    byteVector.push_back( 42 );
    wordVector.push_back( 42 );
    ShowVector( "Garbled bytes as characters output o.O", byteVector );
    ShowVector( "With words, the numbers show as numbers.", wordVector );
    return 0;
}

Il codice sopra produce sciocchezze a causa del design di iostream. Per alcune ragioni al di là della mia comprensione, trattano i byte uint8_t come caratteri, mentre i tipi integrali più grandi vengono trattati come numeri. Qed Bad design.

Inoltre, non posso pensare a come risolvere questo problema. Il tipo potrebbe anche essere un float o un doppio invece ... quindi un cast di 'int' per far capire a iostream che i numeri non i caratteri sono l'argomento non aiuterà.

Dopo aver ricevuto un voto negativo alla mia risposta, forse qualche altra parola di spiegazione ... Il design di IOStream è difettoso in quanto non offre al programmatore un mezzo per indicare come viene trattato un articolo. L'implementazione di IOStream prende decisioni arbitrarie (come trattare uint8_t come un carattere, non come un numero di byte). Questo è un difetto del design IOStream, in quanto cercano di raggiungere l'incredibile.

Il C ++ non consente di classificare un tipo - il linguaggio non ha la funzione. Non esiste nulla che is_number_type () o is_character_type () che IOStream possa usare per fare una scelta automatica ragionevole. Ignorarlo e cercare di cavarsela indovinando è un difetto di progettazione di una biblioteca.

Ammesso, printf () non riuscirebbe ugualmente a funzionare in un'implementazione generica di "ShowVector ()". Ma questa non è una scusa per il comportamento di iostream. Ma è molto probabile che nel caso printf (), ShowVector () sia definito in questo modo:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );

3
La colpa non è (puramente) di iostream. Controlla ciò che il vostro uint8_tè un typedef per. In realtà è un personaggio? Quindi non incolpare gli iostreams per averlo trattato come un char.
Martin Ba,

E se si desidera assicurarsi di ottenere un numero in codice generico, è possibile utilizzare il num_putfacet anziché l'operatore di inserimento del flusso.
Martin Ba,

@Martin Ba Hai ragione - gli standard c / c ++ mantengono aperto il numero di byte di un "int senza segno breve". "unsigned char" è una idiosincrasia della lingua. Se vuoi davvero un byte, devi usare un carattere senza segno. Inoltre, C ++ non consente di imporre restrizioni sugli argomenti del modello, ad esempio "solo numeri" e quindi se ho modificato l'implementazione di ShowVector nella soluzione num_put proposta, ShowVector non potrebbe più visualizzare un vettore di stringhe, giusto? ;)
BitTickler

1
@Martin Bla: cppreference menziona che int8_t è un tipo intero con segno con una larghezza di esattamente 8 bit. Sono d'accordo con l'autore che è strano che tu ottenga l'output di immondizia, anche se è tecnicamente spiegabile dal typedef e dal sovraccarico dei tipi di carattere in iostream . Avrebbe potuto essere risolto avendo un __int8 un tipo vero anziché un typedef.
gast128,

Oh, in realtà è abbastanza facile da risolvere: // Correzioni per std :: ostream che ha rotto il supporto per i tipi unsigned / signed / char // e stampa numeri interi a 8 bit come se fossero caratteri. namespace ostream_fixes {inline std :: ostream & operator << (std :: ostream & os, unsigned char i) {return os << static_cast <unsigned int> (i); } inline std :: ostream & operator << (std :: ostream & os, firmato char i) {return os << static_cast <sign int> (i); }} // namespace ostream_fixes
mcv

1

Gli iostreams in C ++ hanno molti difetti, come notato nelle altre risposte, ma vorrei notare qualcosa in sua difesa.

Il C ++ è praticamente unico tra le lingue in uso serio che rende l'input e l'output variabili semplici per i principianti. In altre lingue, l'input dell'utente tende a coinvolgere la coercizione dei tipi o i formattatori di stringhe, mentre C ++ fa fare al compilatore tutto il lavoro. Lo stesso vale in gran parte per l'output, sebbene C ++ non sia così unico in questo senso. Tuttavia, è possibile eseguire operazioni di I / O formattate piuttosto bene in C ++ senza dover comprendere classi e concetti orientati agli oggetti, cosa utile dal punto di vista pedagogico e senza comprendere la sintassi del formato. Ancora una volta, se insegni ai principianti, questo è un grande vantaggio.

Questa semplicità per i principianti ha un prezzo, il che può renderlo un mal di testa per gestire l'I / O in situazioni più complesse, ma speriamo che a quel punto il programmatore abbia imparato abbastanza da essere in grado di gestirli, o almeno diventato abbastanza vecchio bere.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.