Inside Man
Inside Man

Reputation: 4297

Wrong Inputs will cause the program to exit

Look at this code:

#include <iostream>
using namespace std;
int main()
{
    string s;
    int n;
    float x;
again:
    cout << "Please Type this: ABC456 7.8 9 XYZ\n";
    cin >> s >> n >> x >> s;
    cout << "\nDo you want to try Again(y/n)? ";
    char t;
    cin >> t;
    if (t=='y' || t=='Y')
        goto again;
    return 0;
}

Just try to type "ABC456 7.8 9 XYZ" and press enter, it will cause the program exit before prompting the user to try again. I know the inputs are wrong and they are not in their types, but why it causes the exit? and how to avoid such exit?

Upvotes: 6

Views: 3147

Answers (7)

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20739

Answer https://stackoverflow.com/a/10379322/924727 expalins what happens. About the why, we must go a bit into philosophy.

The C++ stream model is not thought for "human interaction": it is a generic converter of a virtually infinite sequence of characters into a list of space separated "values" to be converted into the supplied "variables".

There is no concept of "input and output interleaving to for a dialog". If you write your input into a text file like myinput.txt (unsing correct input)

ABC456 9 7.8 XYZ
Y
ABC456 5 6.7 XYZ
N

and ron your program from the command prompt like

   myprogram < myinput.txt

your program will run ... and no "pause" can be required to see the output, since no-one is sitting there to see it and answer it.

The program pauses to wait user input not because of cin >>, but because cin is not in fail state and the buffer is empty and the source the buffer remaps is the console. It is the console that waits for '\n' before returning, not cin.

When cin >> n is called...

  • the operator>> function is called, and it ...
  • gets the num_get facet from the stream locale and call its get function that...
  • call the stream buffer sbumpc repeatedly to get the digits and compute the number value.
  • If the buffer has content, it just return its characters one after the other. When no more character are present (or if it is empty) ...
  • The buffer ask the operating system to read from the low level file.
  • If the file is the console, the console internal line editor is invoked:
  • This makes the console to stuck letting the user press character and some controls (like the backspace, for example) until, when Enter is pressed
  • The console line editor returns the line to the operating system that will let the contents available to the input CON file ...
  • That is read by the buffer (after passing the read characters to the cvt locale facet, but this is a detail) that fills up itself.
  • Now do yourself the returns an unrolling.

All this mechanism makes the fact that - if you type more than required - the buffer content remains available to the next >> calls, independently it is or not another program line.

A proper "safer" parse requires, afer an input has been read, the stream state to be cleared and the following content to be ignored up to the next '\n'. This is typically done with

cin.clear(); 
cin.ignore(numeric_limits<std::streamsize>::max(), '\n');

So that, whatever had been typed is discarded, and the next cin>> finds a buffer with no data (just the '\n', that is trimmed as "beginning space"), thus causing the console to go in line edit mode again.

Upvotes: 2

James Kanze
James Kanze

Reputation: 153977

Once the stream detects an error, it is in an error state, and all further attempts to input will be no-ops. Accessing variables read by a stream without first testing whether the read succeeded is undefined behavior, so in theory at least, you're program could do anything. (In practice, if the uninitialized variable is of type char, all you risk is a random value.)

When reading line oriented input, the best solution is to use std::getline. then use the string that was input to construct an std::istringstream to parse the line. This leaves the input stream in a good state, and already synchronized for the next input. I'd use something like:

void
getInput()
{
    std::string line;
    std::cout << "Please type this: ABC456 7.8 9 XYZ" << std::endl;
    std::getline( std::cin, line );
    if ( ! std::cin ) {
        //  Very unexpected error... 
        throw SomethingSerious();
    }
    std::string s;
    int n;
    float f;
    std::istringstream toParse( line );
    toParse >> s >> f >> n >> s;
    if ( ! toParse ) {
        //  Input incorrect...
    }
}

bool
tryAgain()
{
    std::cout << "Do you want to try again (y/n)? ";
    std::string line;
    std::getline( std::cin, line );
    return line.size() == 1 && (line[0] == 'y' || line[0] == 'Y');
        //  But I'd be more tolerant with regards to spaces...
}

bool
oneInput()
{
    getInput();
    return tryAgain();
}

int
main()
{
    while ( oneInput() ) {
    }
    return 0;
}

Upvotes: 1

Binghe Zhai
Binghe Zhai

Reputation: 66

when you call cin.clear(), you should call cin.sync() as the same time.

Upvotes: 1

Potatoswatter
Potatoswatter

Reputation: 137870

When the stream extraction operator >> encounters invalid input, it puts the stream into a mode where no more input is extracted.

You can detect this mode using a Boolean test such as if ( cin ), and reset it using cin.clear(). After doing so, the invalid input remains in cin's input buffer, so you need to process it somehow, such as by ignore.

Better yet, the extraction operator returns cin so you can test while you extract:

if ( ! ( cin >> s >> n >> x >> s ) ) {
    cout << "You fail horribly!\n";
    cin.clear();
    cin.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
}

For more depth see Semantics of flags on basic_ios (and I'm sure there are a few questions on this site that are exact duplicates of this).

Upvotes: 2

phoxis
phoxis

Reputation: 61940

Enter a debug line after the cin

cout << "s = " << s << "   n = " << n << "   x = " << x;  

And run

Please Type this: ABC456 7.8 9 XYZ
ABC456 7.8 9 XYZ
s = 9   n = 7   x = 0.8

Clearly the first ABC456 is read into the string s. The next was a integer therefore only the 7 is read into n and the 0.8 part was read into float x. Now the next input 9 was assigned to s again therefore the final value of s is the string "9". And now the first character of X gets fed into the next cin where it gets assigned to t. To confirm just insert another debug line cout << "\nt = " << t; after you input t. Therefore the if is false with the value of t assigned to 'X' therefore the program exits.

If you input ABC456 7.8 9 YZ instead the program will ask for input another time, as now t will have 'Y'.

Upvotes: 3

codaddict
codaddict

Reputation: 455282

Change

cin >> s >> n >> x >> s;

to

cin >> s >> x >> n >> s;

As you are entering 7.8 as 2nd input but you are collecting it in a integer variable instead of a floating point variable. As a result of this when you enter:

ABC456 7.8 9 XYZ

s gets ABC456, n gets 7 (as it is of int type and the input buffer still has .8 9 XYZ\n in it). Next n gets .8 and finally s gets "9". Now the input buffer has XYZ\n in it. The next time when you read the input into t to get the user's choice, X gets read into t and since it is not y or Y, the loop exits.

Upvotes: 7

Adam Miller
Adam Miller

Reputation: 1783

The program hits a problem or exception when it tries to but the wrong data types together. Perhaps you want to look at the documentation for the >> operator on cin, look for specifics on what it does when it hits wrong input for data types, and look over the cin>> line and your input for any places that this might be occurring so you can validate the input is handled correctly

Upvotes: 1

Related Questions