CodeGym /Corsi /C++ SELF /Stati di std::cin: good/eof/fail/bad

Stati di std::cin: good/eof/fail/bad

C++ SELF
Livello 20, Lezione 0
Disponibile

1. Introduzione

Quando un principiante scrive std::cin >> n;, lo percepisce spesso come un “incantesimo”: «inserisci un numero, per favore». Ma std::cin non è un telepate né uno psicologo. Fa esattamente una cosa onesta: prova a estrarre dal flusso di input dati del formato richiesto. Se non ci riesce — registra il problema nel suo stato interno. E poi — punto importante — quello stato influenza tutte le operazioni di lettura successive.

Se si immagina std::cin come un cassiere, l'operatore >> è il tentativo di «leggere il codice a barre». Se il codice a barre non c'è (al posto delle cifre ci sono lettere), il cassiere non indovina il prezzo per intuizione. Dice: «non posso», si accende la spia d'errore — e finché non risolvete il problema, la cassa non funziona normalmente.

Dettaglio tecnico: gli stream di I/O in C++ sono implementati come classi template (basic_istream, basic_ostream ecc.), e il familiare std::cin è un oggetto concreto per lo stream di char. Questo non è critico per la pratica odierna, ma è utile sapere: «istream» è una famiglia di tipi, non una singola «classe magica».

Flag di stato dello stream: good/eof/fail/bad

Quando parliamo dello “stato di std::cin”, intendiamo un insieme di flag interni. Si possono controllare con i metodi good(), eof(), fail(), bad(). Questi metodi non «riparano» nulla — rispondono semplicemente in modo onesto: in quale modalità vive lo stream in questo momento.

È utile pensare così: lo stato è la ragione per cui possiamo o non possiamo continuare la lettura.

Controllo Cosa significa in parole semplici Motivo tipico
cin.good()
«tutto bene, continuiamo a leggere» nessun errore
cin.eof()
«non ci sono più dati» fine dell'input (file terminato, nella Web‑IDE non è stato fornito altro input)
cin.fail()
«il formato non coincide, non sono riuscito a parsare» si aspettava un int, è arrivato abc
cin.bad()
«problema serio a livello di stream/dispositivo» errore serio di I/O (per il principiante raro)

Importante non confondere: good() non è una «super‑verità» separata, ma piuttosto «nessun flag di errore». Nella pratica good() si usa meno, perché è più comodo ragionare così: «se la lettura è riuscita — procedo», invece di «se lo stream è buono — provo a leggere».

2. Lettura con controllo: if (std::cin >> x)

Uno dei pattern più importanti in C++ (soprattutto nei set di esercizi) è il seguente:

#include <iostream>

int main() {
    int n = 0;

    if (std::cin >> n) {
        std::cout << "n = " << n << '\n';   // per esempio: n = 42
    } else {
        std::cout << "Input failed\n";      // Input failed
    }
}

Qui non c'è nessun «trucco strano», una volta capita la meccanica.

L'espressione std::cin >> n restituisce lo stream (per riferimento). E lo stream può essere convertito a bool (nel senso «sono in uno stato normale o no?»). Quindi if (std::cin >> n) si legge così: «Prova a leggere n. Se ci riesci — esegui il ramo then».

Questa forma è utile perché non ti permette di usare per sbaglio dati spazzatura. Se la lettura fallisce, la variabile può restare con il vecchio valore, e usarla senza verificarne il successo è un errore logico.

Perché «la variabile è comunque qualcosa» è una cattiva notizia

È facile cadere nella trappola psicologica: «ma x è stata stampata, quindi è stata letta». In C++ «la variabile è stata stampata» significa solo che contiene un valore qualsiasi. Ma da dove viene quel valore — è responsabilità vostra saperlo.

Esempio pericoloso:

#include <iostream>

int main() {
    int n = 100;            // supponiamo che avessimo un default
    std::cin >> n;          // se l'input è cattivo, n può restare 100

    std::cout << "n=" << n << '\n'; // n=100 (anche se è stato inserito "oops")
}

Se l'utente inserisce oops, l'output può essere n=100. Al principiante sembra: «oh, funziona!». Poi quel «cento» partecipa a un calcolo di sconto, alla dimensione di un array, alla dimensione di un vector, al numero di iterazioni di un ciclo — e cominciano i guai.

L'idea è semplice: non fidarti del valore di una variabile finché non hai verificato il successo della lettura.

3. fail() e l'«incastro» dopo un errore di formato

La causa più comune del «il mio programma si è bloccato» tra i principianti è il failbit, cioè lo stato fail().

Immaginiamo che ti aspetti un numero:

  • input: 10 — ottimo
  • input: abc — non è un numero

Al secondo passo lo stream non riesce a estrarre un int. Cosa fa?

Imposta il flag fail, e i «caratteri cattivi» (a, b, c) rimangono nel buffer di input. Dopo di ciò ogni tentativo successivo di std::cin >> int vede di nuovo gli stessi abc e fallisce ancora. Si ottiene l'effetto «giorno della marmotta»: chiedi un numero, e lo stream continua a scontrarsi con le stesse lettere.

Esempio minimo:

#include <iostream>

int main() {
    int a = 0;
    int b = 0;

    std::cin >> a;
    std::cin >> b;

    std::cout << "fail=" << std::cin.fail() << '\n';
    std::cout << "a=" << a << " b=" << b << '\n';
}

Se l'utente inserisce 123 abc, vedrete una situazione del genere:

  • a è stato letto: 123
  • b non è stato letto, perché abc non si converte in int
  • fail=1
  • b è rimasto uguale a 0 (o al valore precedente, se c'era)

Conclusione chiave: dopo fail() lo stream non si «autorigenera». Questo è fatto intenzionalmente: altrimenti il programma continuerebbe come se nulla fosse successo, e otterreste errori silenziosi e molto sgradevoli.

Nella prossima lezione impareremo a «riparare» lo stream correttamente, ma oggi è importante fissare la meccanica: fail() non è un «una tantum fallito», ma è «ora sono in modalità errore finché non mi sistemano».

4. eof() e bad(): ragioni diverse per l'arresto

Perché non si può controllare eof() in anticipo

Con EOF (end‑of‑file, fine dell'input) i principianti spesso hanno una tragedia filosofica: vogliono chiedere allo stream «ci sono ancora dati?» e poi leggere. Ma nella realtà eof() diventa vero dopo un tentativo di lettura fallito, quando lo stream ha capito: «non ci sono più dati».

L'approccio corretto di solito è: «leggi finché si riesce a leggere».

#include <iostream>

int main() {
    int x = 0;

    while (std::cin >> x) {
        std::cout << "read: " << x << '\n';
    }

    if (std::cin.eof()) {
        std::cout << "Stopped by EOF\n";
    } else if (std::cin.fail()) {
        std::cout << "Stopped by format error\n";
    } else if (std::cin.bad()) {
        std::cout << "Stopped by bad stream\n";
    }
}

Questo ciclo gestisce con cura due scenari diversi:

  • l'utente/la piattaforma ha fornito una serie di numeri e l'input è terminato → il ciclo finisce e eof() sarà true
  • l'utente a metà ha «rotto il formato» → il ciclo finisce e fail() sarà true

Sono ragioni diverse per l'arresto, per questo le distinguiamo.

bad() — raro, ma è il «pulsante rosso»

Di bad() si può dire onestamente: nei compiti sulla Web‑IDE lo incontrerete di rado. Ma sapere che esiste è utile — almeno per pensare correttamente alla natura degli errori.

Se fail() è «non ho potuto parsare il formato», allora bad() è «lo stream di input si è rotto». Per esempio, problemi con il dispositivo, errori gravi di buffer, ecc. Nei semplici esercizi didattici quasi non emerge, ma in programmi reali che leggono da file o rete può succedere.

Posizione pratica per un principiante: se incontri bad() — di solito termini l'esecuzione, perché lì spesso non c'è molto da riparare a livello didattico. In questo contesto considereremo bad() come «avaria», e fail() come «errore dell'utente».

5. Lettura come mini‑automa a stati

Quando scrivi programmi che fanno input, è utile avere in mente un semplice diagramma: un tentativo di lettura porta lo stream in uno dei possibili stati, e il tuo codice deve reagire di conseguenza.

flowchart TD
    A["Tentativo di lettura: std::cin >> x"] --> B{Successo?}
    B -- "sì" --> C["Usiamo x e procediamo"]
    B -- "no" --> D{Motivo?}
    D -- "EOF" --> E["Terminiamo: non ci sono più dati"]
    D -- "fail" --> F["Errore di formato: l'utente ha inserito il valore sbagliato"]
    D -- "bad" --> G["Errore serio dello stream: terminazione d'emergenza"]

Non è «teoria fine a se stessa». È il tuo anti‑debugger: quando il programma si comporta in modo strano, non indovini le cause, ma ti chiedi: «siamo finiti in fail? in eof?».

6. Pratica: leggiamo comandi e terminiamo correttamente

Per non lasciare il tema nel vuoto, facciamo un piccolo passo verso una applicazione didattica.

Immaginiamo che in precedenza (nel giorno sul struct) abbiamo creato un semplice modello di compito:

  • id — numero intero
  • title — parola corta senza spazi (per ora evitiamo getline, per non mescolare gli argomenti)

Oggi non «ripareremo» gli input errati (è la lezione successiva), ma impareremo a riconoscere correttamente la ragione dell'arresto.

#include <iostream>
#include <string>
#include <vector>

struct Task {
    int id = 0;
    std::string title;
};

int main() {
    std::vector<Task> tasks;

    Task t;
    while (std::cin >> t.id >> t.title) {
        tasks.push_back(t);
    }

    std::cout << "Loaded tasks: " << tasks.size() << '\n';

    if (std::cin.eof()) {
        std::cout << "Reason: EOF\n";
    } else if (std::cin.fail()) {
        std::cout << "Reason: format error\n";
    } else if (std::cin.bad()) {
        std::cout << "Reason: bad stream\n";
    }
}

Come funzionerà:

Se l'input è il seguente:

1 wash
2 code
3 sleep

e poi l'input termina, otterrete Reason: EOF.

Se l'input è il seguente:

1 wash
two code
3 sleep

allora il ciclo si fermerà sulla riga two code, e otterrete Reason: format error. Questo è un comportamento onesto: non fingiamo che sia tutto a posto.

Notate l'idea: finché non sappiamo ripristinare lo stream, la soluzione più sicura è fermarsi e riportare la ragione. Questo rende il programma prevedibile.

7. Errori tipici nel lavoro con gli stati di std::cin

Errore №1: usare i dati senza controllare std::cin >> x.
Lo scenario più comune: si legge un int, non si verifica il successo, e poi si usa x nei calcoli. Con input errato non avrai un'eccezione, ma «spazzatura silenziosa» (il vecchio valore della variabile). Si risolve con disciplina: o if (std::cin >> x), o il ciclo while (std::cin >> x).

Errore №2: controllare eof() prima della lettura e basare la logica su questo.
eof() di solito diventa vero solo dopo aver provato a leggere e aver fallito per fine dati. Quindi la logica «finché non eof, leggi» spesso dà un'iterazione in più e comportamenti strani. È più affidabile leggere «finché si legge»: while (std::cin >> x).

Errore №3: confondere fail() ed eof() e scrivere un unico messaggio “non riuscito”.
Fine dell'input ed errore di formato sono situazioni diverse. In una non ci sono più dati (situazione normale), nell'altra l'utente ha inserito il valore sbagliato (è un errore). Se le mescoli, non capirai cosa è successo e non potrai scrivere un comportamento corretto.

Errore №4: provare a leggere ancora dopo fail() sperando che si sistemi da solo.
Dopo fail() lo stream non avanza, e i caratteri errati di solito rimangono nel buffer. Le letture successive non «saltano» la spazzatura. Per questo sembra che il programma sia «bloccato» o che «ignori l'input». Nella lezione successiva vedremo come ripristinare lo stream, ma oggi è importante ricordare il fatto: lo stato fail() è appiccicoso.

Errore №5: ignorare bad() come se fosse la stessa cosa di fail().
Anche se nei compiti didattici è raro, per significato bad() è una situazione più grave. fail() spesso significa «l'utente ha inserito il valore sbagliato», mentre bad() indica che «il meccanismo di input si è rotto». A livello di corso base la reazione sensata a bad() è terminare il programma e non cercare di continuare come se nulla fosse.

1
Compito
C++ SELF, livello 20, lezione 0
Bloccato
Un solo input
Un solo input
1
Compito
C++ SELF, livello 20, lezione 0
Bloccato
Registro di lettura
Registro di lettura
1
Compito
C++ SELF, livello 20, lezione 0
Bloccato
Somma delle letture
Somma delle letture
1
Compito
C++ SELF, livello 20, lezione 0
Bloccato
Caricamento delle attività
Caricamento delle attività
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION