—ARTICLE START—
In this beginner-friendly guide, you’ll learn C++ exception handling step by step: C++ try catch throw explained for beginners. By the end, you’ll know what exceptions are, why we use them, and how to write simple, safe code that handles errors gracefully. If you’re new to our C++ series, you can also explore more tutorials in the CodDesire C++ section.
What is exception handling in C++ and why is it used?
Exception handling is how C++ reports and handles errors at runtime. Instead of returning an error code (like -1) from a function, the function can throw an object (an exception). The runtime then looks for a matching catch block to handle it. This separates normal code from error-handling code, which makes programs clearer and more reliable.
Why use exceptions?
- They keep the main logic clean by moving error handling into catch blocks.
- They automatically unwind the stack (local objects get destroyed) so resources are not leaked when errors happen.
- They can carry helpful messages and types (e.g.,
std::runtime_error,std::out_of_range).
Modern best practices to remember:
- Throw by value; catch by
constreference. - Prefer types derived from
std::exception(e.g.,std::invalid_argument,std::runtime_error). - Only use exceptions for exceptional situations, not routine control flow.
Basic syntax: try, catch, and throw
Syntax overview for how to use try catch in C++:
try { /* code that may fail */ }starts a protected block.throwcreates and sends an exception.catch (const SomeType& e) { /* handle */ }handles a matching exception type.
try { ... }throw an exception objectcatch handles the errorthrow; to rethrow, or continue after the handlercatch (...) last.#class="cd-package">include <iostream>
#class="cd-package">include <stdexcept>
double safe_divide(double a, double b) {
if (b == 0.0) {
throw std::runtime_error("division by zero");
}
return a / b;
}
int main() {
try {
std::cout << safe_divide(10, 2) << "n";
std::cout << safe_divide(5, 0) << "n"; // throws
std::cout << "This line will not run after exceptionn";
} catch (const std::runtime_error& e) {
std::cout << "Caught runtime_error: " << e.what() << "n";
} catch (const std::exception& e) { // fallback for std::exception types
std::cout << "Caught std::exception: " << e.what() << "n";
}
std::cout << "Program continuesn";
}5
Caught runtime_error: division by zero
Program continuesSimple C++ try catch example for students
Here is a simple, readable example that throws and catches an index error. This is a beginner friendly C++ exception handling tutorial pattern you’ll use a lot.
#class="cd-package">include <iostream>
#class="cd-package">include <vector>
#class="cd-package">include <stdexcept>
int get_score(const std::vector<int>& scores, std::size_t i) {
if (i >= scores.size()) {
throw std::out_of_range("index out of bounds");
}
return scores.at(i); // safe now
}
int main() {
std::vector<int> scores = {85, 90, 78};
try {
std::cout << "Second: " << get_score(scores, 1) << "n";
std::cout << "Fifth: " << get_score(scores, 4) << "n"; // throws
} catch (const std::out_of_range& e) {
std::cout << "Out of range: " << e.what() << "n";
}
}Second: 90
Out of range: index out of boundsStep-by-step guide to throwing and catching exceptions in C++
- Write the normal code inside a
tryblock. - Throw when you detect an error:
throw std::runtime_error("message"); - Catch the most specific exceptions first, then more general ones.
- Optionally use
catch (...)as a last resort to catch anything.
#class="cd-package">include <iostream>
#class="cd-package">include <string>
#class="cd-package">include <stdexcept>
int main() {
const std::string inputs[] = {"20", "abc", "9999999999999"};
for (const auto& s : inputs) {
try {
int age = std::stoi(s); // may throw invalid_argument or out_of_range
std::cout << "Parsed age: " << age << "n";
} catch (const std::out_of_range&) { // more specific
std::cout << "Number too large to fit in intn";
} catch (const std::invalid_argument&) { // then less specific
std::cout << "Input was not a numbern";
} catch (...) { // always put this last
std::cout << "Unknown parsing errorn";
}
}
}Parsed age: 20
Input was not a number
Number too large to fit in intHow the throw keyword works (C++ throw keyword example)
The throw keyword creates an exception object and transfers control to the nearest matching catch. When you need to add context and still let a higher level handle it, rethrow the current exception with plain throw; (not throw e;).
#class="cd-package">include <iostream>
#class="cd-package">include <stdexcept>
#class="cd-package">include <string>
void process(const std::string& s) {
try {
int n = std::stoi(s);
if (n <= 0) {
throw std::runtime_error("value must be > 0");
}
std::cout << "OK: " << n << "n";
} catch (const std::exception&) {
std::cout << "process() saw an error, rethrowing...n";
throw; // rethrow preserves the original exception type and message
}
}
int main() {
try {
process("0"); // will cause runtime_error
} catch (const std::exception& e) {
std::cout << "Main caught: " << e.what() << "n";
}
}process() saw an error, rethrowing...
Main caught: value must be > 0How to make a custom exception class in C++
You can define your own exception types to make error handling clearer. Derive from std::exception (commonly std::runtime_error) so you can reuse what() and integrate well with existing code.
#class="cd-package">include <iostream>
#class="cd-package">include <stdexcept>
#class="cd-package">include <string>
class NegativeBalanceError : class="cd-keyword cd-access">public std::runtime_error {
class="cd-keyword cd-access">public:
explicit NegativeBalanceError(const std::string& msg)
: std::runtime_error(msg) {}
};
int main() {
double balance = 100.0;
double withdraw = 120.0;
try {
if (withdraw > balance) {
throw NegativeBalanceError("withdrawal exceeds balance");
}
balance -= withdraw;
std::cout << "New balance: " << balance << "n";
} catch (const NegativeBalanceError& e) {
std::cout << "Bank error: " << e.what() << "n";
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << "n";
}
}Bank error: withdrawal exceeds balanceC++ exceptions vs return codes
Beginners often ask whether to use exceptions or return codes. Here’s a quick comparison:
| Aspect | Exceptions | Return codes |
|---|---|---|
| Readability | Separates error handling from normal logic | Error checks clutter the main flow |
| Propagation | Automatic via stack unwinding; RAII-friendly | Manual propagation needed; easy to forget |
| Performance | No cost on the non-throw path in common ABIs; throwing is expensive | Constant overhead on every call |
| Use case | Exceptional, unexpected failures | Expected outcomes, simple validation, hot paths |
| Interoperability | Requires exceptions enabled in the toolchain | Works even if exceptions are disabled |
Common mistakes with try catch in C++ and how to fix them
- Catching by value (risk of slicing)
- Mistake:
catch (std::exception e) - Fix:
catch (const std::exception& e)— keeps the dynamic type and avoids copying.
- Mistake:
- Rethrowing with
throw e;- Mistake:
catch (...) { throw e; }— may change the exception type. - Fix: use plain
throw;to preserve the current exception.
- Mistake:
- Catching base before derived
- Mistake: catching
std::exceptionbeforestd::out_of_rangemakes the later catch unreachable. - Fix: order from most specific to most general; place
catch (...)last.
- Mistake: catching
- Throwing from destructors
- Mistake: letting exceptions escape a destructor, especially during stack unwinding, leads to
std::terminate. - Fix: handle errors inside destructors (log, swallow), or expose a non-destructor cleanup function that can fail.
- Mistake: letting exceptions escape a destructor, especially during stack unwinding, leads to
- Using removed dynamic exception specifications
- Mistake:
void f() throw(std::bad_alloc);— removed in C++17. - Fix: use
noexceptto state non-throwing functions when appropriate.
- Mistake:
Modern tips for beginners
- Throw by value, catch by const reference — safe and efficient.
- Use standard exception types like
std::invalid_argument,std::range_error,std::out_of_range,std::runtime_error. noexcept: mark functionsnoexceptwhen they can’t throw. If an exception escapes anoexceptfunction, the program callsstd::terminate.- Don’t overuse exceptions: prefer return codes or types like
std::optionalor (in C++23)std::expected<T,E>when failures are common and part of normal control flow. - Unwinding awareness: during exception unwinding, local objects are destroyed in reverse order. This is why RAII works well with exceptions.
- Detecting unwinding: if you must, use
std::uncaught_exceptions()(replaces removedstd::uncaught_exception) to detect if stack unwinding is in progress. - Cross-thread errors: to pass exceptions between threads, use
std::exception_ptr,std::current_exception(), andstd::rethrow_exception().
- Wrap only the smallest risky section in
try { ... }(keep scope tight). - Throw by value with a clear message:
throw std::runtime_error("why it failed"); - Catch by
constreference:catch (const std::runtime_error& e) - Order handlers: specific → general →
catch (...)last. - Need to add context but defer handling? Log and use
throw;to rethrow. - Let RAII manage cleanup; never let exceptions escape a destructor.
- Use
noexceptfor functions that must not throw. - For frequent, expected failures, consider
std::optional/std::expectedinstead of exceptions.
Mini practice: try it yourself
- Write a function
read_percentage()that throwsstd::out_of_rangeif the value is not in [0, 100]. Catch and print a friendly message. - Create a custom exception
FileOpenErrorderived fromstd::runtime_error. Throw it when simulating a failed open; catch it and print the file path. - Modify one of the examples to add a logging
catchthat rethrows using plainthrow;and handle it inmain().
FAQ: C++ try catch throw explained for beginners
What is exception handling in C++ and why is it used?
It’s a structured way to report runtime errors using throw and handle them with catch blocks. It improves clarity, avoids scattered error checks, and works well with RAII to prevent resource leaks.
How do try, catch, and throw work in C++ with a simple example?
Put risky code in a try block, use throw when a problem occurs, and handle it in a matching catch. See the “Basic syntax” and “Simple example” sections above for runnable demos.
What happens if no catch block matches an exception in C++?
The runtime keeps unwinding to outer try blocks. If no handler is found (including in main()), the program calls std::terminate and usually aborts.
Should beginners use exceptions or return codes in C++?
Use exceptions for unexpected, exceptional errors. Use return codes (or types like std::optional/std::expected) for common, expected outcomes or performance-critical paths.
How do I create and use a custom exception class in C++?
Derive from std::exception (often std::runtime_error) and pass a message to the base constructor. Throw your type and catch by const reference. See the “custom exception class” example above.
Common patterns to remember
- Always order
catchblocks from most-specific to most-general. - Use
catch (...)sparingly and only last. - When adding context but letting others handle the error, log and rethrow with
throw;. - Avoid throwing from destructors; handle failures internally or provide explicit cleanup functions.
Sources / Further reading
- C++ exceptions — language overview (cppreference)
- Throwing exceptions (cppreference)
- noexcept specifier (cppreference)
- std::uncaught_exceptions (cppreference)
- try, throw, and catch (Microsoft Learn)
- Modern C++ best practices for exceptions (Microsoft Learn)
- C++ Core Guidelines — Error handling (E.*)
Keep practicing and revisit this page whenever you need a refresher on C++ error handling for beginners. For more step-by-step C++ lessons, return to our C++ tutorials hub.
—ARTICLE END—


