What is the best way to do input validation in C++ with cin?
My brother recently started learning C++. He told me a problem he encountered while trying to validate input in a simple program. He had a text menu where the user entered an integer choice
, if they entered an invalid choice, they would be asked to enter it again (do while loop). However, if the user entered a string instead of an int, the code would break.
I read various questions on stackoverflow and told him to rewrite his code along the lines of:
#include<iostream>
using namespace std;
int main()
{
int a;
do
{
cout<<"\nEnter a number:"
cin>>a;
if(cin.fail())
{
//Clear the fail state.
cin.clear();
//Ignore the rest of the wrong user input, till the end of the line.
cin.ignore(std::numeric_limits<std::streamsize>::max(),\
'\n');
}
}while(true);
return 0;
}
While this worked ok, I also tried a few other ideas:
1. Using a try catch block. It didn't work. I think this is because an exception is not raised due to bad input.
2. I tried if(! cin){//Do Something}
which didn't work either. I haven't yet figured this one out.
3. Thirdly, I tried inputting a fixed length string and then parsing it. I would use atoi(). Is this standards compliant and portable? Should I write my own parsing function?
4. If write a class that uses cin, but dynamically does this kind of error detection, perhaps by determining the type of the input variable at runtime, would it have too much overhead? Is it even possible?
I would like to know what is the best way to do this kind of checking, what are the best practices?
I would like to add that while I am not new to writing C++ code, I am new to writing good standards compliant code. I am trying to unlearn bad practices and learn the right ones. I would be much obliged if answerers give a detailed explanation.
EDIT: I see that litb has answered one of my previous edits. I'll post that code here for reference.
#include<iostream>
using namespace std;
int main()
{
int a;
bool inputCompletionFlag = true;
do
{
cout<<"\nEnter a number:"
cin>>a;
if(cin.fail())
{
//Clear the fail state.
cin.clear();
//Ignore the rest of the wrong user input, till the end of the line.
cin.ignore(std::numeric_limits<std::streamsize>::max(),\
'\n');
}
else
{
inputCompletionFlag = false;
}
}while(!inputCompletionFlag);
return 0;
}
This code fails on input like "1asdsdf". I didn't know how to fix it but litb has posted a great answer. :)
Here is code you could use to make sure you also reject things like
42crap
Where non-number characters follow the number. If you read the whole line and then parse it and execute actions appropriately it will possibly require you to change the way your program works. If your program read your number from different places until now, you then have to put one central place that parses one line of input, and decides on the action. But maybe that's a good thing too - so you could increase the readability of the code that way by having things separated: Input - Processing - Output
Anyway, here is how you can reject the number-non-number of above. Read a line into a string, then parse it with a stringstream
:
std::string getline() {
std::string str;
std::getline(std::cin, str);
return str;
}
int choice;
std::istringstream iss(getline());
iss >> choice >> std::ws;
if(iss.fail() || !iss.eof()) {
// handle failure
}
It eats all trailing whitespace. When it hits the end-of-file of the stringstream while reading the integer or trailing whitespace, then it sets the eof-bit, and we check that. If it failed to read any integer in the first place, then the fail or bad bit will have been set.
Earlier versions of this answer used std::cin
directly - but std::ws
won't work well together with std::cin
connected to a terminal (it will block instead waiting for the user to input something), so we use a stringstream
for reading the integer.
Answering some of your questions:
Question: 1. Using a try catch block. It didn't work. I think this is because an exception is not raised due to bad input.
Answer: Well, you can tell the stream to throw exceptions when you read something. You use the istream::exceptions
function, which you tell for which kind of error you want to have an exception thrown:
iss.exceptions(ios_base::failbit);
I did never use it. If you do that on std::cin
, you will have to remember to restore the flags for other readers that rely on it not throwing. Finding it way easier to just use the functions fail, bad to ask for the state of the stream.
Question: 2. I tried if(!cin){ //Do Something }
which didn't work either. I haven't yet figured this one out.
Answer: That could come from the fact that you gave it something like "42crap". For the stream, that is completely valid input when doing an extraction into an integer.
Question: 3. Thirdly, I tried inputting a fixed length string and then parsing it. I would use atoi(). Is this standards compliant and portable? Should I write my own parsing function?
Answer: atoi is Standard Compliant. But it's not good when you want to check for errors. There is no error checking, done by it as opposed to other functions. If you have a string and want to check whether it contains a number, then do it like in the initial code above.
There are C-like functions that can read directly from a C-string. They exist to allow interaction with old, legacy code and writing fast performing code. One should avoid them in programs because they work rather low-level and require using raw naked pointers. By their very nature, they can't be enhanced to work with user defined types either. Specifically, this talks about the function "strtol" (string-to-long) which is basically atoi with error checking and capability to work with other bases (hex for example).
Question: 4. If I write a class that uses cin, but dynamically do this kind of error detection, perhaps by determining the type of the input variable at runtime, will it have too much overhead? Is it even possible?
Answer: Generally, you don't need to care too much about overhead here (if you mean runtime-overhead). But it depends specifically on where you use that class. That question will be very important if you are writing a high performance system that processes input and needs to have high throughout. But if you need to read input from a terminal or a file, you already see what this comes down to: Waiting for the user to input something takes really so long, you don't need to watch runtime costs at this point anymore on this scale.
If you mean code overhead - well it depends on how the code is implemented. You would need to scan your string that you read - whether it contains a number or not, whether some arbitrary string. Depending on what you want to scan (maybe you have a "date" input, or a "time" input format too. Look into boost.date_time
for that), your code can become arbitrarily complex. For simple things like classifying between number or not, I think you can get away with small amount of code.
This is what I do with C but it's probably applicable for C++ as well.
Input everything as a string.
Then, and only then, parse the string into what you need. It's sometimes better to code your own than try to bend someone else's to your will.
- In order to get the exceptions with iostreams you need to set the proper exception flag for the stream.
- And I would use get_line to get the whole line of input and then handle it accordingly - use lexical_cast, regular expressions (for example Boost Regex or Boost Xpressive, parse it with Boost Spirit, or just use some kind of appropriate logic
What I would do is twofold: First, try to validate the input, and extract the data, using a regular expression, if the input is somewhat not trivial. It can be very helpful also even if the input is just a series of numbers.
Then, I like to use boost::lexical_ cast, that can raise a bad_ lexical_ cast exception if the input cannot be converted.
In your example:
std::string in_str;
cin >> in_str;
// optionally, test if it conforms to a regular expression, in case the input is complex
// Convert to int? this will throw bad_lexical_cast if cannot be converted.
int my_int = boost::lexical_cast<int>(in_str);