Coming Soon
This lesson is currently being developed
Detecting and handling errors
Master error detection and handling strategies.
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.
9.4 — Detecting and handling errors
In this lesson, you'll learn strategies for detecting errors in your programs and handling them gracefully when they occur.
What is error detection and handling?
Error detection is the process of identifying when something goes wrong in your program. Error handling is deciding what to do when an error is detected. Together, they help create robust programs that can deal with unexpected situations without crashing.
Think of error handling like defensive driving - you anticipate problems (other drivers, bad weather, mechanical issues) and have plans to handle them safely.
Types of errors to detect
Runtime errors
Errors that occur during program execution:
#include <iostream>
int divide(int numerator, int denominator)
{
// Problem: Division by zero will crash the program
return numerator / denominator;
}
int safeDivide(int numerator, int denominator, bool& success)
{
// Error detection: Check for division by zero
if (denominator == 0)
{
std::cout << "Error: Cannot divide by zero!" << std::endl;
success = false;
return 0; // Return safe default value
}
success = true;
return numerator / denominator;
}
int main()
{
std::cout << "Unsafe division:" << std::endl;
// This would crash: std::cout << divide(10, 0) << std::endl;
std::cout << "Crash prevented for demonstration" << std::endl;
std::cout << "\nSafe division:" << std::endl;
bool success;
int result = safeDivide(10, 0, success);
if (success)
std::cout << "Result: " << result << std::endl;
else
std::cout << "Division failed - using default value: " << result << std::endl;
return 0;
}
Output:
Unsafe division:
Crash prevented for demonstration
Safe division:
Error: Cannot divide by zero!
Division failed - using default value: 0
Input validation errors
Checking that user input meets expected criteria:
#include <iostream>
int getValidAge()
{
int age;
std::cout << "Enter your age (0-150): ";
std::cin >> age;
// Error detection: Check input range
if (age < 0)
{
std::cout << "Error: Age cannot be negative!" << std::endl;
return -1; // Error code
}
if (age > 150)
{
std::cout << "Error: Age seems unrealistic (>150)!" << std::endl;
return -1; // Error code
}
return age; // Valid age
}
std::string getValidGrade()
{
std::string grade;
std::cout << "Enter letter grade (A, B, C, D, F): ";
std::cin >> grade;
// Error detection: Check valid grade letters
if (grade == "A" || grade == "B" || grade == "C" || grade == "D" || grade == "F")
{
return grade; // Valid grade
}
std::cout << "Error: Invalid grade '" << grade << "'" << std::endl;
return ""; // Error indicator (empty string)
}
int main()
{
int age = getValidAge();
if (age != -1) // Check for error
{
std::cout << "Valid age entered: " << age << std::endl;
}
std::string grade = getValidGrade();
if (!grade.empty()) // Check for error
{
std::cout << "Valid grade entered: " << grade << std::endl;
}
return 0;
}
Sample Output:
Enter your age (0-150): 25
Valid age entered: 25
Enter letter grade (A, B, C, D, F): X
Error: Invalid grade 'X'
Resource errors
Problems with files, memory, or other system resources:
#include <iostream>
#include <fstream>
#include <string>
bool readFileContent(const std::string& filename, std::string& content)
{
std::ifstream file(filename);
// Error detection: Check if file opened successfully
if (!file.is_open())
{
std::cout << "Error: Could not open file '" << filename << "'" << std::endl;
return false;
}
// Read file content
std::string line;
content.clear();
while (std::getline(file, line))
{
content += line + "\n";
}
// Check for read errors
if (file.bad())
{
std::cout << "Error: Failed to read from file '" << filename << "'" << std::endl;
return false;
}
file.close();
return true; // Success
}
int main()
{
std::string content;
// Try to read a file that probably doesn't exist
if (readFileContent("nonexistent.txt", content))
{
std::cout << "File content:\n" << content << std::endl;
}
else
{
std::cout << "Failed to read file - handling error gracefully" << std::endl;
}
return 0;
}
Output:
Error: Could not open file 'nonexistent.txt'
Failed to read file - handling error gracefully
Error handling strategies
Return error codes
Use special return values to indicate errors:
#include <iostream>
#include <vector>
// Return -1 for error, valid index otherwise
int findElement(const std::vector<int>& vec, int target)
{
if (vec.empty())
{
std::cout << "Error: Cannot search empty vector" << std::endl;
return -1; // Error code
}
for (int i = 0; i < static_cast<int>(vec.size()); ++i)
{
if (vec[i] == target)
{
return i; // Success: return index
}
}
return -1; // Not found (could also be separate from error)
}
double calculateSquareRoot(double number, bool& success)
{
if (number < 0)
{
std::cout << "Error: Cannot calculate square root of negative number" << std::endl;
success = false;
return 0.0; // Error value
}
success = true;
return std::sqrt(number); // Note: Need to include <cmath> in real code
}
int main()
{
std::vector<int> numbers = {10, 20, 30, 40};
int index = findElement(numbers, 25);
if (index == -1)
{
std::cout << "Element not found or error occurred" << std::endl;
}
else
{
std::cout << "Element found at index: " << index << std::endl;
}
bool success;
double result = calculateSquareRoot(-4.0, success);
if (!success)
{
std::cout << "Square root calculation failed" << std::endl;
}
return 0;
}
Output:
Element not found or error occurred
Error: Cannot calculate square root of negative number
Square root calculation failed
Use output parameters
Pass variables by reference to return both results and error status:
#include <iostream>
#include <string>
enum class ParseResult
{
Success,
InvalidFormat,
OutOfRange
};
ParseResult parseInteger(const std::string& input, int& result)
{
// Check for empty string
if (input.empty())
{
return ParseResult::InvalidFormat;
}
// Simple parsing (this is a simplified version)
bool negative = false;
std::string digits = input;
if (input[0] == '-')
{
negative = true;
digits = input.substr(1);
}
else if (input[0] == '+')
{
digits = input.substr(1);
}
// Check if all characters are digits
for (char c : digits)
{
if (c < '0' || c > '9')
{
return ParseResult::InvalidFormat;
}
}
// Convert to integer (simplified - doesn't handle overflow properly)
int value = 0;
for (char c : digits)
{
value = value * 10 + (c - '0');
}
if (negative)
value = -value;
result = value;
return ParseResult::Success;
}
void demonstrateParsing()
{
std::vector<std::string> testInputs = {"123", "-456", "abc", "12x34", ""};
for (const std::string& input : testInputs)
{
int result;
ParseResult status = parseInteger(input, result);
std::cout << "Input: '" << input << "' -> ";
switch (status)
{
case ParseResult::Success:
std::cout << "Success, value: " << result << std::endl;
break;
case ParseResult::InvalidFormat:
std::cout << "Invalid format" << std::endl;
break;
case ParseResult::OutOfRange:
std::cout << "Out of range" << std::endl;
break;
}
}
}
int main()
{
demonstrateParsing();
return 0;
}
Output:
Input: '123' -> Success, value: 123
Input: '-456' -> Success, value: -456
Input: 'abc' -> Invalid format
Input: '12x34' -> Invalid format
Input: '' -> Invalid format
Early returns for error handling
Exit functions early when errors are detected:
#include <iostream>
#include <vector>
double calculateAverage(const std::vector<int>& numbers)
{
// Early return for empty vector
if (numbers.empty())
{
std::cout << "Error: Cannot calculate average of empty vector" << std::endl;
return 0.0;
}
// Early return for vector with only zeros (might cause confusion)
bool allZeros = true;
for (int num : numbers)
{
if (num != 0)
{
allZeros = false;
break;
}
}
if (allZeros)
{
std::cout << "Warning: All values are zero" << std::endl;
return 0.0; // Technically correct, but worth noting
}
// Calculate sum
int sum = 0;
for (int num : numbers)
{
sum += num;
}
// Return average
return static_cast<double>(sum) / numbers.size();
}
void processGrades()
{
std::vector<int> grades1 = {}; // Empty
std::vector<int> grades2 = {0, 0, 0}; // All zeros
std::vector<int> grades3 = {85, 92, 78, 88, 95}; // Valid grades
std::cout << "Class 1 average: " << calculateAverage(grades1) << std::endl;
std::cout << "Class 2 average: " << calculateAverage(grades2) << std::endl;
std::cout << "Class 3 average: " << calculateAverage(grades3) << std::endl;
}
int main()
{
processGrades();
return 0;
}
Output:
Error: Cannot calculate average of empty vector
Class 1 average: 0
Warning: All values are zero
Class 2 average: 0
Class 3 average: 87.6
Defensive programming
Validate all inputs
Check preconditions before processing:
#include <iostream>
#include <vector>
#include <string>
class BankAccount
{
private:
double balance;
std::string accountNumber;
public:
BankAccount(const std::string& accNum, double initialBalance)
{
// Validate account number
if (accNum.empty() || accNum.length() < 5)
{
std::cout << "Error: Invalid account number" << std::endl;
accountNumber = "INVALID";
balance = 0.0;
return;
}
// Validate initial balance
if (initialBalance < 0)
{
std::cout << "Error: Initial balance cannot be negative" << std::endl;
accountNumber = accNum;
balance = 0.0;
return;
}
accountNumber = accNum;
balance = initialBalance;
std::cout << "Account created successfully" << std::endl;
}
bool withdraw(double amount)
{
// Validate account state
if (accountNumber == "INVALID")
{
std::cout << "Error: Cannot operate on invalid account" << std::endl;
return false;
}
// Validate withdrawal amount
if (amount <= 0)
{
std::cout << "Error: Withdrawal amount must be positive" << std::endl;
return false;
}
if (amount > balance)
{
std::cout << "Error: Insufficient funds" << std::endl;
return false;
}
balance -= amount;
std::cout << "Withdrawal successful. New balance: $" << balance << std::endl;
return true;
}
bool deposit(double amount)
{
// Validate account state
if (accountNumber == "INVALID")
{
std::cout << "Error: Cannot operate on invalid account" << std::endl;
return false;
}
// Validate deposit amount
if (amount <= 0)
{
std::cout << "Error: Deposit amount must be positive" << std::endl;
return false;
}
balance += amount;
std::cout << "Deposit successful. New balance: $" << balance << std::endl;
return true;
}
double getBalance() const
{
if (accountNumber == "INVALID")
{
std::cout << "Error: Cannot get balance of invalid account" << std::endl;
return -1; // Error indicator
}
return balance;
}
};
int main()
{
// Test valid account
BankAccount account1("12345", 1000.0);
account1.deposit(200.0);
account1.withdraw(150.0);
std::cout << std::endl;
// Test invalid account creation
BankAccount account2("", -500.0);
account2.deposit(100.0); // Should fail
std::cout << std::endl;
// Test invalid operations
account1.withdraw(-50.0); // Negative amount
account1.withdraw(2000.0); // Insufficient funds
return 0;
}
Output:
Account created successfully
Deposit successful. New balance: $1200
Withdrawal successful. New balance: $1050
Error: Invalid account number
Error: Initial balance cannot be negative
Account created successfully
Error: Cannot operate on invalid account
Error: Withdrawal amount must be positive
Error: Insufficient funds
Range checking for arrays and containers
Always verify indices are valid:
#include <iostream>
#include <vector>
class SafeVector
{
private:
std::vector<int> data;
public:
void push_back(int value)
{
data.push_back(value);
}
bool get(int index, int& value) const
{
// Validate index
if (index < 0)
{
std::cout << "Error: Index cannot be negative (" << index << ")" << std::endl;
return false;
}
if (index >= static_cast<int>(data.size()))
{
std::cout << "Error: Index " << index << " out of bounds (size: " << data.size() << ")" << std::endl;
return false;
}
value = data[index];
return true;
}
bool set(int index, int value)
{
// Validate index
if (index < 0)
{
std::cout << "Error: Index cannot be negative (" << index << ")" << std::endl;
return false;
}
if (index >= static_cast<int>(data.size()))
{
std::cout << "Error: Index " << index << " out of bounds (size: " << data.size() << ")" << std::endl;
return false;
}
data[index] = value;
return true;
}
int size() const
{
return static_cast<int>(data.size());
}
void printAll() const
{
std::cout << "Vector contents: ";
for (int i = 0; i < static_cast<int>(data.size()); ++i)
{
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
};
int main()
{
SafeVector vec;
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
vec.printAll();
// Test valid access
int value;
if (vec.get(1, value))
{
std::cout << "vec[1] = " << value << std::endl;
}
// Test invalid access
vec.get(-1, value); // Negative index
vec.get(5, value); // Too large index
vec.set(10, 999); // Invalid set operation
return 0;
}
Output:
Vector contents: 10 20 30
vec[1] = 20
Error: Index cannot be negative (-1)
Error: Index 5 out of bounds (size: 3)
Error: Index 10 out of bounds (size: 3)
Error logging and debugging
Simple error logging
Keep track of errors for debugging:
#include <iostream>
#include <vector>
#include <string>
class ErrorLogger
{
private:
std::vector<std::string> errorLog;
public:
void logError(const std::string& function, const std::string& error)
{
std::string entry = "[" + function + "] " + error;
errorLog.push_back(entry);
std::cout << "ERROR: " << entry << std::endl;
}
void logWarning(const std::string& function, const std::string& warning)
{
std::string entry = "[" + function + "] WARNING: " + warning;
errorLog.push_back(entry);
std::cout << "WARNING: " << entry << std::endl;
}
void printErrorLog() const
{
std::cout << "\n=== Error Log ===" << std::endl;
if (errorLog.empty())
{
std::cout << "No errors logged." << std::endl;
}
else
{
for (const std::string& entry : errorLog)
{
std::cout << entry << std::endl;
}
}
std::cout << "=================" << std::endl;
}
};
// Global error logger
ErrorLogger logger;
double safeDivision(double a, double b)
{
if (b == 0.0)
{
logger.logError("safeDivision", "Division by zero attempted");
return 0.0;
}
if (b > -0.0001 && b < 0.0001 && b != 0.0)
{
logger.logWarning("safeDivision", "Division by very small number: " + std::to_string(b));
}
return a / b;
}
int validateAndSum(const std::vector<int>& numbers)
{
if (numbers.empty())
{
logger.logError("validateAndSum", "Empty vector provided");
return 0;
}
int sum = 0;
int negativeCount = 0;
for (int num : numbers)
{
if (num < 0)
{
negativeCount++;
}
sum += num;
}
if (negativeCount > 0)
{
logger.logWarning("validateAndSum",
"Found " + std::to_string(negativeCount) + " negative numbers");
}
return sum;
}
int main()
{
std::cout << "Testing error detection and logging:" << std::endl;
// Test division
std::cout << "10 / 2 = " << safeDivision(10.0, 2.0) << std::endl;
std::cout << "10 / 0 = " << safeDivision(10.0, 0.0) << std::endl;
std::cout << "10 / 0.00001 = " << safeDivision(10.0, 0.00001) << std::endl;
// Test sum validation
std::vector<int> numbers1 = {1, 2, 3, 4, 5};
std::vector<int> numbers2 = {};
std::vector<int> numbers3 = {1, -2, 3, -4, 5};
std::cout << "Sum 1: " << validateAndSum(numbers1) << std::endl;
std::cout << "Sum 2: " << validateAndSum(numbers2) << std::endl;
std::cout << "Sum 3: " << validateAndSum(numbers3) << std::endl;
// Print error log
logger.printErrorLog();
return 0;
}
Output:
Testing error detection and logging:
10 / 2 = 5
ERROR: [safeDivision] Division by zero attempted
10 / 0 = 0
WARNING: [safeDivision] WARNING: Division by very small number: 0.000010
10 / 0.00001 = 1e+06
Sum 1: 15
ERROR: [validateAndSum] Empty vector provided
Sum 2: 0
WARNING: [validateAndSum] WARNING: Found 2 negative numbers
Sum 3: 3
=== Error Log ===
[safeDivision] Division by zero attempted
[safeDivision] WARNING: Division by very small number: 0.000010
[validateAndSum] Empty vector provided
[validateAndSum] WARNING: Found 2 negative numbers
=================
Error handling best practices
1. Fail fast
Detect and report errors as soon as possible:
#include <iostream>
class Rectangle
{
private:
double width, height;
public:
Rectangle(double w, double h)
{
// Fail fast: Check parameters immediately
if (w <= 0 || h <= 0)
{
std::cout << "Error: Rectangle dimensions must be positive" << std::endl;
width = 1.0; // Default to safe values
height = 1.0;
}
else
{
width = w;
height = h;
}
}
double area() const
{
// No need to check here - constructor ensures valid state
return width * height;
}
void resize(double w, double h)
{
// Fail fast: Validate before changing state
if (w <= 0 || h <= 0)
{
std::cout << "Error: Cannot resize to non-positive dimensions" << std::endl;
return; // Don't change the object state
}
width = w;
height = h;
}
};
2. Use clear error messages
Make error messages helpful for debugging:
#include <iostream>
#include <string>
bool validatePassword(const std::string& password, std::string& errorMessage)
{
if (password.length() < 8)
{
errorMessage = "Password too short (minimum 8 characters, got " +
std::to_string(password.length()) + ")";
return false;
}
if (password.length() > 50)
{
errorMessage = "Password too long (maximum 50 characters, got " +
std::to_string(password.length()) + ")";
return false;
}
bool hasDigit = false;
bool hasUpper = false;
bool hasLower = false;
for (char c : password)
{
if (c >= '0' && c <= '9') hasDigit = true;
if (c >= 'A' && c <= 'Z') hasUpper = true;
if (c >= 'a' && c <= 'z') hasLower = true;
}
if (!hasDigit)
{
errorMessage = "Password must contain at least one digit";
return false;
}
if (!hasUpper)
{
errorMessage = "Password must contain at least one uppercase letter";
return false;
}
if (!hasLower)
{
errorMessage = "Password must contain at least one lowercase letter";
return false;
}
return true; // Password is valid
}
int main()
{
std::vector<std::string> testPasswords = {
"abc", // Too short
"abcdefgh", // No digits or uppercase
"ABCDEFGH1", // No lowercase
"abcdefgh1", // No uppercase
"Password123" // Valid
};
for (const std::string& pwd : testPasswords)
{
std::string error;
if (validatePassword(pwd, error))
{
std::cout << "'" << pwd << "' is valid" << std::endl;
}
else
{
std::cout << "'" << pwd << "' is invalid: " << error << std::endl;
}
}
return 0;
}
Output:
'abc' is invalid: Password too short (minimum 8 characters, got 3)
'abcdefgh' is invalid: Password must contain at least one digit
'ABCDEFGH1' is invalid: Password must contain at least one lowercase letter
'abcdefgh1' is invalid: Password must contain at least one uppercase letter
'Password123' is valid
3. Provide recovery mechanisms
When possible, allow programs to recover from errors:
#include <iostream>
#include <vector>
class RobustCalculator
{
public:
static double average(const std::vector<double>& numbers)
{
if (numbers.empty())
{
std::cout << "Warning: Empty vector, returning 0" << std::endl;
return 0.0; // Reasonable default
}
double sum = 0.0;
int validCount = 0;
for (double num : numbers)
{
// Skip invalid numbers (NaN, infinity)
if (std::isnan(num) || std::isinf(num))
{
std::cout << "Warning: Skipping invalid number in average calculation" << std::endl;
continue;
}
sum += num;
validCount++;
}
if (validCount == 0)
{
std::cout << "Error: No valid numbers to average, returning 0" << std::endl;
return 0.0;
}
return sum / validCount;
}
};
int main()
{
std::vector<double> numbers1 = {1.0, 2.0, 3.0, 4.0, 5.0};
std::vector<double> numbers2 = {};
std::vector<double> numbers3 = {1.0, std::numeric_limits<double>::quiet_NaN(), 3.0};
std::cout << "Average 1: " << RobustCalculator::average(numbers1) << std::endl;
std::cout << "Average 2: " << RobustCalculator::average(numbers2) << std::endl;
std::cout << "Average 3: " << RobustCalculator::average(numbers3) << std::endl;
return 0;
}
Summary
Error detection and handling are essential for creating robust programs:
Error detection strategies:
- Validate all inputs before processing
- Check preconditions and postconditions
- Monitor resource operations (files, memory)
- Use range checking for arrays and containers
Error handling approaches:
- Return error codes or status values
- Use output parameters to return both results and status
- Exit functions early when errors occur
- Log errors for debugging and monitoring
Best practices:
- Fail fast - detect errors as early as possible
- Provide clear, actionable error messages
- Allow recovery when feasible
- Validate inputs at function boundaries
- Use defensive programming techniques
Good error handling makes your programs more reliable, easier to debug, and more user-friendly. It's an investment in code quality that pays dividends when problems arise.
Quiz
- What's the difference between error detection and error handling?
- Why is it important to validate function parameters?
- What are some ways to indicate that a function encountered an error?
- What does "fail fast" mean in error handling?
- Why should error messages be specific and informative?
Practice exercises
Add error detection and handling to these functions:
- Write a safe array access function that checks bounds
- Create a robust string-to-integer conversion function
- Implement a division function that handles all error cases
- Design a file reader that handles missing files and read errors
Explore More Courses
Discover other available courses while this lesson is being prepared.
Browse CoursesLesson Discussion
Share your thoughts and questions