Modern Error Handling in C++
🎭 The Story of Two Messengers
Imagine you work at a pizza delivery shop. When something goes wrong with an order, you have two ways to tell the boss:
- Error Codes: You quietly write a note and hand it back. The boss might read it… or might forget to check.
- Exceptions: You shout “STOP! Something’s wrong!” Everyone hears it, and work stops until it’s fixed.
Both methods work. But each has its own superpower and weakness. Let’s explore!
🔢 Error Codes: The Silent Note
What Are Error Codes?
Error codes are numbers or values that tell you “something went wrong” — but quietly.
int divide(int a, int b, int& result) {
if (b == 0) {
return -1; // Error code: division by zero!
}
result = a / b;
return 0; // Success code
}
How It Works
Think of it like a traffic light:
- 🟢 0 = Success! Go ahead!
- 🔴 -1 = Stop! Something’s wrong!
Using Error Codes
int answer;
int status = divide(10, 0, answer);
if (status == -1) {
// Handle the error
std::cout << "Oops! Can't divide by zero!";
} else {
std::cout << "Answer: " << answer;
}
✅ Good Things About Error Codes
| Advantage | Why It Matters |
|---|---|
| Fast | No extra work for the computer |
| Predictable | You know exactly what happens |
| Simple | Easy to understand |
⚠️ The Problem
You can IGNORE error codes!
int answer;
divide(10, 0, answer); // Ignored the return!
std::cout << answer; // 💥 Garbage value!
The computer doesn’t force you to check. It’s like getting a warning letter but throwing it away without reading!
🚨 Exceptions: The Loud Alarm
What Are Exceptions?
Exceptions are like fire alarms. When something bad happens, they force everyone to pay attention!
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Cannot divide by zero!");
}
return a / b;
}
How Exceptions Work
graph TD A["Code Runs"] --> B{Error?} B -->|Yes| C["throw Exception"] C --> D["Jump to catch Block"] B -->|No| E["Continue Normally"] D --> F["Handle Error"]
Catching Exceptions
try {
int result = divide(10, 0);
std::cout << result;
} catch (const std::exception& e) {
std::cout << "Error: " << e.what();
}
✅ Good Things About Exceptions
| Advantage | Why It Matters |
|---|---|
| Can’t ignore | Code stops if not caught |
| Cleaner code | No error checks everywhere |
| Rich info | Carries detailed messages |
⚠️ The Problem
Exceptions can be slow and make code harder to follow. It’s like setting off a fire alarm for every small problem — even when you just burnt toast!
⚖️ Error Codes vs Exceptions: The Showdown
| Feature | Error Codes | Exceptions |
|---|---|---|
| Speed | 🚀 Very fast | 🐢 Slower |
| Can ignore? | ⚠️ Yes (dangerous!) | ✅ No (must handle) |
| Code clarity | 😕 Cluttered with checks | 😊 Cleaner main path |
| Memory safe? | 😕 Manual cleanup needed | ✅ Automatic cleanup |
| Best for | Performance-critical code | Complex error scenarios |
When to Use What?
graph TD A["Error Happens"] --> B{Expected Often?} B -->|Yes| C["Use Error Codes"] B -->|No| D{Serious Problem?} D -->|Yes| E["Use Exceptions"] D -->|No| C
Rule of thumb:
- Expected failures (like “file not found”) → Error codes
- Unexpected disasters (like “out of memory”) → Exceptions
🌟 std::expected: The Best of Both Worlds!
The Problem It Solves
What if we could have:
- ✅ The safety of exceptions (can’t ignore!)
- ✅ The speed of error codes (no throwing!)
- ✅ Rich error information in one package?
Enter std::expected! (C++23)
What Is std::expected?
Think of it like a gift box that contains either:
- 🎁 Your present (the success value), OR
- 📝 A sorry note (the error information)
But NEVER both!
#include <expected>
std::expected<int, std::string> divide(int a, int b) {
if (b == 0) {
return std::unexpected("Cannot divide by zero!");
}
return a / b; // Success!
}
Using std::expected
auto result = divide(10, 2);
if (result.has_value()) {
std::cout << "Answer: " << result.value();
} else {
std::cout << "Error: " << result.error();
}
Even Cleaner with value_or()
auto result = divide(10, 0);
int answer = result.value_or(-1); // Use -1 if error
The Magic of std::expected
graph TD A["std::expected"] --> B{Check has_value} B -->|true| C["Get value safely"] B -->|false| D["Get error info"] C --> E["Use the result!"] D --> F["Handle the error!"]
Why It’s Amazing
| Feature | std::expected |
|---|---|
| Ignore error? | 🚫 No! Compiler warns you |
| Performance | 🚀 No exception overhead |
| Rich errors | ✅ Any error type you want |
| Clean syntax | ✅ Modern C++ style |
Real-World Example
std::expected<User, Error> findUser(int id) {
if (id <= 0) {
return std::unexpected(Error::InvalidId);
}
if (!database.connected()) {
return std::unexpected(Error::NoConnection);
}
return database.getUser(id); // Success!
}
// Using it:
auto user = findUser(42);
if (user) {
std::cout << "Hello, " << user->name;
} else {
switch (user.error()) {
case Error::InvalidId:
std::cout << "Bad ID!";
break;
case Error::NoConnection:
std::cout << "Database offline!";
break;
}
}
🎯 Quick Decision Guide
When to Use What?
| Situation | Best Choice |
|---|---|
| Simple success/fail | Error codes |
| Critical errors that must stop everything | Exceptions |
| Modern C++23 projects | std::expected |
| Performance-critical code | Error codes or std::expected |
| Rich error information needed | Exceptions or std::expected |
🏁 Summary
You’ve learned three powerful ways to handle errors in C++:
-
Error Codes 🔢
- Fast and simple
- But easy to ignore (dangerous!)
-
Exceptions 🚨
- Can’t be ignored
- But slower and can be messy
-
std::expected 🌟 (C++23)
- Fast like error codes
- Safe like exceptions
- The modern choice!
Remember the pizza shop:
- Error codes = quiet notes (might be ignored)
- Exceptions = loud alarms (can’t be ignored)
- std::expected = a smart delivery tracker (fast AND reliable!)
You now have the tools to handle ANY error like a pro! 🎉
