Coming Soon
This lesson is currently being developed
Stream states and input validation
Handle stream errors and validate input.
What to Expect
Comprehensive explanations with practical examples
Interactive coding exercises to practice concepts
Knowledge quiz to test your understanding
Step-by-step guidance for beginners
Development Status
Content is being carefully crafted to provide the best learning experience
Preview
Early Preview Content
This content is still being developed and may change before publication.
28.5 — Stream states and input validation
In this lesson, you'll learn about stream states and how to handle input validation effectively. Understanding stream states is crucial for writing robust programs that gracefully handle invalid input and I/O errors.
Understanding stream states
Every C++ stream maintains internal state flags that indicate whether operations are succeeding or failing. These states help you detect and respond to various error conditions.
#include <iostream>
int main()
{
int number;
std::cout << "Enter a number: ";
std::cin >> number;
std::cout << "Stream state after input:" << std::endl;
std::cout << "good(): " << std::cin.good() << std::endl;
std::cout << "fail(): " << std::cin.fail() << std::endl;
std::cout << "bad(): " << std::cin.bad() << std::endl;
std::cout << "eof(): " << std::cin.eof() << std::endl;
return 0;
}
Example Run (valid input):
Enter a number: 42
Stream state after input:
good(): 1
fail(): 0
bad(): 0
eof(): 0
Example Run (invalid input):
Enter a number: hello
Stream state after input:
good(): 0
fail(): 1
bad(): 0
eof(): 0
The four stream state flags
1. goodbit (good())
- Indicates no errors have occurred
- All operations are working normally
- Returns
true
when all other flags arefalse
2. failbit (fail())
- Set when an operation fails (e.g., wrong data type)
- Stream is still usable after clearing the error
- Most common error state for input validation
3. badbit (bad())
- Indicates a serious error (e.g., hardware failure)
- Stream may be unusable
- Less common than failbit
4. eofbit (eof())
- Set when end-of-file is reached
- Often used to detect when input is exhausted
#include <iostream>
void displayStreamState(std::istream& stream, const std::string& context)
{
std::cout << context << ":" << std::endl;
std::cout << " good(): " << stream.good() << std::endl;
std::cout << " fail(): " << stream.fail() << std::endl;
std::cout << " bad(): " << stream.bad() << std::endl;
std::cout << " eof(): " << stream.eof() << std::endl;
std::cout << std::endl;
}
int main()
{
int number;
displayStreamState(std::cin, "Initial state");
std::cout << "Enter a number: ";
std::cin >> number;
displayStreamState(std::cin, "After input attempt");
if (std::cin.fail())
{
std::cout << "Input failed!" << std::endl;
std::cin.clear(); // Clear error flags
displayStreamState(std::cin, "After clearing error");
}
else
{
std::cout << "Successfully read: " << number << std::endl;
}
return 0;
}
Checking stream states for validation
Basic validation pattern
#include <iostream>
int main()
{
int number;
std::cout << "Enter an integer: ";
if (std::cin >> number)
{
std::cout << "Successfully read: " << number << std::endl;
}
else
{
std::cout << "Failed to read integer!" << std::endl;
// Clear error state
std::cin.clear();
// Skip invalid input
std::string invalidInput;
std::cin >> invalidInput;
std::cout << "Invalid input was: " << invalidInput << std::endl;
}
return 0;
}
Example Run:
Enter an integer: abc
Failed to read integer!
Invalid input was: abc
Robust input validation loop
#include <iostream>
#include <limits>
int getValidInteger(const std::string& prompt, int min = INT_MIN, int max = INT_MAX)
{
int value;
while (true)
{
std::cout << prompt;
if (std::cin >> value)
{
// Check if value is in range
if (value >= min && value <= max)
{
// Clear any remaining characters in the buffer
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}
else
{
std::cout << "Value must be between " << min << " and " << max << std::endl;
}
}
else
{
std::cout << "Invalid input. Please enter an integer." << std::endl;
// Clear error flags
std::cin.clear();
// Remove invalid input from buffer
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
}
int main()
{
int age = getValidInteger("Enter your age (1-150): ", 1, 150);
int score = getValidInteger("Enter test score (0-100): ", 0, 100);
std::cout << "Age: " << age << std::endl;
std::cout << "Score: " << score << std::endl;
return 0;
}
Example Run:
Enter your age (1-150): abc
Invalid input. Please enter an integer.
Enter your age (1-150): -5
Value must be between 1 and 150
Enter your age (1-150): 25
Enter test score (0-100): 150
Value must be between 0 and 100
Enter test score (0-100): 95
Age: 25
Score: 95
Clearing stream states and buffers
The clear() method
#include <iostream>
int main()
{
int number;
std::cout << "Enter a number: ";
std::cin >> number;
if (std::cin.fail())
{
std::cout << "Input failed." << std::endl;
std::cout << "Before clear() - fail(): " << std::cin.fail() << std::endl;
std::cin.clear(); // Clear error flags
std::cout << "After clear() - fail(): " << std::cin.fail() << std::endl;
// Note: Invalid input is still in the buffer!
std::cout << "Next character in buffer: ";
char ch = std::cin.get();
std::cout << "'" << ch << "'" << std::endl;
}
return 0;
}
The ignore() method
#include <iostream>
#include <limits>
int main()
{
int number;
std::cout << "Enter a number: ";
std::cin >> number;
if (std::cin.fail())
{
std::cout << "Input failed." << std::endl;
// Clear error flags
std::cin.clear();
// Remove bad input from buffer
std::cout << "Ignoring invalid input..." << std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Buffer cleared. Try again:" << std::endl;
std::cout << "Enter a number: ";
if (std::cin >> number)
{
std::cout << "Successfully read: " << number << std::endl;
}
}
return 0;
}
Different types of validation
Validating different data types
#include <iostream>
#include <string>
#include <limits>
template<typename T>
T getValidInput(const std::string& prompt)
{
T value;
while (true)
{
std::cout << prompt;
if (std::cin >> value)
{
// Clear buffer
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}
else
{
std::cout << "Invalid input. Please try again." << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
}
int main()
{
int age = getValidInput<int>("Enter your age: ");
double height = getValidInput<double>("Enter your height in feet: ");
char grade = getValidInput<char>("Enter your letter grade: ");
std::cout << "\nSummary:" << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "Height: " << height << " feet" << std::endl;
std::cout << "Grade: " << grade << std::endl;
return 0;
}
Custom validation functions
#include <iostream>
#include <string>
#include <limits>
bool isValidEmail(const std::string& email)
{
// Simple validation: must contain @ and at least one dot after @
size_t atPos = email.find('@');
if (atPos == std::string::npos || atPos == 0 || atPos == email.length() - 1)
return false;
size_t dotPos = email.find('.', atPos);
return dotPos != std::string::npos && dotPos < email.length() - 1;
}
std::string getValidEmail(const std::string& prompt)
{
std::string email;
while (true)
{
std::cout << prompt;
std::getline(std::cin, email);
if (isValidEmail(email))
{
return email;
}
else
{
std::cout << "Invalid email format. Please include @ and domain." << std::endl;
}
}
}
bool isValidPhoneNumber(const std::string& phone)
{
// Simple validation: must be 10 digits, may contain hyphens or spaces
int digitCount = 0;
for (char c : phone)
{
if (isdigit(c))
{
digitCount++;
}
else if (c != '-' && c != ' ' && c != '(' && c != ')')
{
return false; // Invalid character
}
}
return digitCount == 10;
}
std::string getValidPhoneNumber(const std::string& prompt)
{
std::string phone;
while (true)
{
std::cout << prompt;
std::getline(std::cin, phone);
if (isValidPhoneNumber(phone))
{
return phone;
}
else
{
std::cout << "Invalid phone number. Must contain exactly 10 digits." << std::endl;
}
}
}
int main()
{
std::cout << "User Registration" << std::endl;
std::cout << "=================" << std::endl;
std::string email = getValidEmail("Enter email address: ");
std::string phone = getValidPhoneNumber("Enter phone number: ");
std::cout << "\nRegistration successful!" << std::endl;
std::cout << "Email: " << email << std::endl;
std::cout << "Phone: " << phone << std::endl;
return 0;
}
Menu-driven input validation
#include <iostream>
#include <limits>
int getMenuChoice(int minChoice, int maxChoice)
{
int choice;
while (true)
{
std::cout << "\nMenu Options:" << std::endl;
for (int i = minChoice; i <= maxChoice; ++i)
{
std::cout << i << ". Option " << i << std::endl;
}
std::cout << "0. Exit" << std::endl;
std::cout << "Enter choice (" << minChoice << "-" << maxChoice << ", 0 to exit): ";
if (std::cin >> choice)
{
if (choice == 0 || (choice >= minChoice && choice <= maxChoice))
{
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return choice;
}
else
{
std::cout << "Invalid choice. Please select " << minChoice
<< "-" << maxChoice << " or 0 to exit." << std::endl;
}
}
else
{
std::cout << "Invalid input. Please enter a number." << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
}
int main()
{
int choice;
do
{
choice = getMenuChoice(1, 3);
switch (choice)
{
case 1:
std::cout << "You selected Option 1" << std::endl;
break;
case 2:
std::cout << "You selected Option 2" << std::endl;
break;
case 3:
std::cout << "You selected Option 3" << std::endl;
break;
case 0:
std::cout << "Goodbye!" << std::endl;
break;
}
}
while (choice != 0);
return 0;
}
Handling mixed input types
#include <iostream>
#include <sstream>
#include <string>
bool parseNameAndAge(const std::string& input, std::string& name, int& age)
{
std::istringstream iss(input);
// Try to read name (everything except the last word)
std::string word;
std::vector<std::string> words;
while (iss >> word)
{
words.push_back(word);
}
if (words.size() < 2)
{
return false; // Need at least name and age
}
// Try to parse the last word as age
std::istringstream ageStream(words.back());
if (!(ageStream >> age) || !ageStream.eof())
{
return false; // Last word is not a valid integer
}
// Combine all but the last word as the name
name = words[0];
for (size_t i = 1; i < words.size() - 1; ++i)
{
name += " " + words[i];
}
return true;
}
int main()
{
std::string input, name;
int age;
while (true)
{
std::cout << "Enter name and age (e.g., 'John Smith 25'): ";
std::getline(std::cin, input);
if (parseNameAndAge(input, name, age))
{
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
break;
}
else
{
std::cout << "Invalid format. Please enter name followed by age." << std::endl;
}
}
return 0;
}
Stream state restoration
#include <iostream>
#include <limits>
class StreamStateGuard
{
private:
std::istream& stream;
std::ios_base::iostate originalState;
public:
StreamStateGuard(std::istream& s) : stream(s), originalState(s.rdstate()) {}
~StreamStateGuard()
{
// Restore original state on destruction
stream.clear(originalState);
}
void clearErrors()
{
stream.clear();
}
};
int main()
{
int number;
{
StreamStateGuard guard(std::cin);
std::cout << "Enter a number: ";
std::cin >> number;
if (std::cin.fail())
{
std::cout << "Input failed, but state will be restored." << std::endl;
guard.clearErrors();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
else
{
std::cout << "Successfully read: " << number << std::endl;
}
}
// Stream state is automatically restored here
std::cout << "Stream state after guard destruction:" << std::endl;
std::cout << "good(): " << std::cin.good() << std::endl;
return 0;
}
Best practices for input validation
✅ Good practices:
#include <iostream>
#include <limits>
// Always provide clear error messages
int getPositiveInteger(const std::string& prompt)
{
int value;
while (true)
{
std::cout << prompt;
if (std::cin >> value)
{
if (value > 0)
{
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}
else
{
std::cout << "Error: Value must be positive (greater than 0)." << std::endl;
}
}
else
{
std::cout << "Error: Please enter a valid integer." << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
}
// Validate ranges and provide helpful feedback
double getPercentage(const std::string& prompt)
{
double value;
while (true)
{
std::cout << prompt;
if (std::cin >> value)
{
if (value >= 0.0 && value <= 100.0)
{
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return value;
}
else
{
std::cout << "Error: Percentage must be between 0 and 100." << std::endl;
}
}
else
{
std::cout << "Error: Please enter a valid number." << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
}
❌ Common mistakes:
#include <iostream>
int main()
{
int number;
// Bad: Not checking input success
std::cout << "Enter a number: ";
std::cin >> number;
std::cout << "You entered: " << number << std::endl; // May be garbage!
// Bad: Not clearing error state
if (std::cin.fail())
{
std::cout << "Error occurred" << std::endl;
// Forgot std::cin.clear() - error state persists!
}
// Bad: Not clearing buffer
std::cin.clear(); // Cleared error state but not the buffer
std::cin >> number; // May immediately fail again!
return 0;
}
Summary
Effective stream state management and input validation involves:
- Understanding stream states:
good()
,fail()
,bad()
, andeof()
- Checking input success: Always verify that stream operations succeed
- Clearing error states: Use
clear()
to reset error flags - Buffer management: Use
ignore()
to remove invalid input - Validation loops: Keep asking until valid input is received
- Range checking: Validate that input values are within expected ranges
- Custom validation: Create functions for complex validation rules
- Error messages: Provide clear, helpful feedback to users
Proper input validation makes your programs more robust and user-friendly, preventing crashes and unexpected behavior from invalid input.
Quiz
- What are the four stream state flags and what do they indicate?
- What's the difference between
clear()
andignore()
? - Why should you check the success of input operations?
- How do you validate that an integer is within a specific range?
- What happens if you don't clear the input buffer after a failed extraction?
Practice exercises
Test your input validation skills with these exercises:
-
User Registration System: Create a program that validates username (alphanumeric only), password (minimum 8 characters), and email format.
-
Calculator with Validation: Build a calculator that validates numeric input and operator input, handling all possible error conditions gracefully.
-
Date Validator: Write a program that reads a date in MM/DD/YYYY format and validates that it's a real date (considering leap years).
-
Survey System: Create a survey that asks multiple questions with different types of validation (age ranges, yes/no questions, ratings 1-5, etc.).
Explore More Courses
Discover other available courses while this lesson is being prepared.
Browse CoursesLesson Discussion
Share your thoughts and questions