views:

652

answers:

5

I can't seem to figure out why this falls into a loop after getting non-int input. I've tried cin.flush(), which doesn't seem to exist, cin.clear(), which seems like it should work, even cin.sync() after reading someone else post about it working, but didn't seem to make much sense. Also tried cin.bad().

Thank you very much for any help

Please enter the first number: f Sorry, I don't think that's a number?

Please enter the first number: Sorry, I don't think that's a number?

Please enter the first number: Sorry, I don't think that's a number?

Please enter the first number: Sorry, I don't think that's a number?

Please enter the first number: Sorry, I don't think that's a number?Sorry, you d on't get any more tries. Press any key to continue . . .

#include <iostream>
using namespace std;

int main(){
    int entry;
    int attempts = 1;
    int result;
    while(attempts <= 5) {
        try {
            cout << "\n\nPlease enter the first number: ";
            cin >> entry;
            if (cin.fail())
                throw "Sorry, I don't think that's a number?";
            if (entry < 0)
                throw "Sorry, no negative numbers. Try something else? ";
            cout << "\nNow the second number: ";
            cin >> entry;
            cin.clear();
            cin.get();
        }
        catch (char* error) {
            cout << error;
            attempts++;
        }
    }
    if (attempts > 5)
        cout << "Sorry, you don\'t get any more tries.\n";
    system("pause");
    return 0;
}
A: 

Why are you doing this with exceptions? You're not going to kill the program on input, so you shouldn't throw an exception.

Just print out your error message and attempt to read in again.

Liz Albin
-1. Ignores the issue at hand, which was why the code "falls into a loop after getting non-int input." Printing the error immediately and using `continue` would yield the same results.
Rob Kennedy
A: 

It looks like you would be better off with iostream's native exceptions. Enable with

cin.exceptions( ios::failbit );

try {
   …
} catch( ios_base::failure & ) {
  cin.clear();
  …
}

Never, ever throw an an object not derived from std::exception, and especially not a native type like char*.

Potatoswatter
Except if you specifically want to signal something so that it doesn't get caught in the error handling (the interruption support of the proposed thread API of C++0x used this trick to interrupt a thread).
Tronic
-1. Making the proposed change wouldn't solve the problem at hand, which has nothing to do with what type of exception is thrown or whether the exception is thrown manually or automatically by the stream.
Rob Kennedy
@Rob: But I *did* address the problem at hand in the example code…
Potatoswatter
No you didn't. The call to `clear` resets the error state, but the bad input remains in the buffer.
Rob Kennedy
+5  A: 

You should think carefully what you want to do if user gives invalid input in this case. Usually in these cases the best solution is to read one line from the input and throw it away.

Try putting cin.clear() and std::cin.ignore(std::numeric_limits<streamsize>::max(),'\n'); in your catch clause. cin.clear() clears the failure state in cin, and cin.ignore() throws away rest of the line waiting in the input buffer.

(And yes, you probably should rethink your use of exceptions).

hrnt
A: 

Exceptions should be used to handle exceptional, unexpected situations. Incorrect input from a user is neither unexpected nor exceptional -- it's more or less the norm. Personally, I tend to just ignore most bad input completely (when it can't be prevented). When (and only when) they enter something unusable repeatedly is it even worth pointing it out to them. As such, I'd tend to write the code something like:

char ch;
int attempts = 0;

std::cout << "Please enter the first number: ";
do { 
    cin >> ch;
    attempts++;
    if (attempts > 5)
        std::cerr << "The only allowable inputs are '0' through '9'\n";            
} while (cin.good() && !isdigit(ch));
int first_number = ch - '0';

This reads the input as a character, so it's always removed from the input stream. Then it attempts to validate the input, and if it fails, attempts to read again. Of course, you might want/need to get a little more elaborate, such as reading an entire line, attempting to convert it to a number, and reading another line if that fails.

Jerry Coffin
Reading characters is a very bad way to read numbers (that presumably can be more than one digit).
Tronic
@Tronic:Which, of course, is why I said you might want to read a whole line and attempt to convert that. The reason for doing it, however, is that (at least normally) anything the user can enter will be read into chars successfully, so they don't stay in the input buffer, causing problems like the OP is asking about.
Jerry Coffin
+1  A: 

The most straight-forward (but not usually the easiest nor the fastest) way of dealing with validation of line-based input is to always read it line at a time. This way no extra whitespace (such as linefeed characters) is left unread in the buffer in any case, and discarding erroneous input is also quite automatic.

// Infinite loop for retrying until successful
while (true) {
    // Ask the user something
    std::cout << prompt;
    // Read the answer (one full line)
    std::string line;
    if (!std::getline(std::cin, line))
      throw std::runtime_error("End of input while expecting a value");
    // Put the line read into iss for further parsing
    std::istringstream iss(line);
    int val;
    // Read val from iss and verify that reading was successful and
    // that all input was consumed
    if (iss >> val && iss.get() == EOF) return val;
    std::cout << "Invalid input, try again!\n";
}

It is fun to make a BASIC style input function out of this:

template <typename Val> void input(std::string const& prompt, Val& val) {
    // (the above code goes here, slightly adjusted)
}

int main() {
    int w;
    double l;
    input("Enter weight in kg: ", w);
    input("Enter length in m: ", l);
    std::cout << "BMI: " << w / (l * l) << std::endl;
}

Notes for the pedantics who were going to downvote me:

  • function input should be specialized for std::string
  • exceptions thrown by the input function should be caught in main
Tronic