Programming Language & Its Applications β Generic Programming and Exception Handling, NEC licence examination syllabus (Nepal Engineering Council).
try, throw and catch β separating the detection of an error from the code that can deal with it.
fopen returns NULL and malloc returns NULL, and studies of real codebases found that a large fraction of return values were never checked β every unchecked one a latent crash. An exception cannot be ignored: if nobody handles it, the program terminates loudly rather than continuing with corrupt state. That is the whole design argument. But it is contested β Google's C++ style guide bans exceptions, game engines disable them for performance, and Rust chose Result instead. Search "why Google style guide bans exceptions" to hear the strongest case against, then "exception safety guarantees" for the case in favour.#include <iostream>
#include <stdexcept>
#include <string>
class Guard {
std::string name;
public:
Guard(std::string n) : name(std::move(n)) {
std::cout << " +" << name << "\n";
}
~Guard() { std::cout << " -" << name
<< " (destructor ran)\n"; }
};
void level3(bool fail) {
Guard g("g3");
if (fail) throw std::runtime_error("failure in level3");
std::cout << " level3 finished normally\n";
}
void level2(bool fail) { Guard g("g2"); level3(fail); }
void level1(bool fail) { Guard g("g1"); level2(fail); }
int main() {
std::cout << " --- normal path ---\n";
level1(false);
std::cout << " --- throwing path ---\n";
try {
level1(true);
std::cout << " NEVER REACHED\n";
}
catch (const std::runtime_error &e) {
std::cout << " caught: " << e.what() << "\n";
}
std::cout << " --- execution continues here ---\n";
/* you can throw ANY type β but there are good reasons
to throw only exception classes (see below) */
try { throw 42; }
catch (int n) { std::cout << " caught an int: " << n << "\n"; }
try { throw std::string("a string"); }
catch (const std::string &s) {
std::cout << " caught a string: " << s << "\n";
}
/* NO implicit conversion: catch(double) will NOT
catch a thrown int */
try {
try { throw 7; }
catch (double d) { std::cout << " never: " << d << "\n"; }
}
catch (int n) {
std::cout << " int fell through to the outer catch: "
<< n << "\n";
}
return 0;
}
Output:
--- normal path ---
+g1
+g2
+g3
level3 finished normally
-g3 (destructor ran)
-g2 (destructor ran)
-g1 (destructor ran)
--- throwing path ---
+g1
+g2
+g3
-g3 (destructor ran)
-g2 (destructor ran)
-g1 (destructor ran)
caught: failure in level3
--- execution continues here ---
caught an int: 42
caught a string: a string
int fell through to the outer catch: 7
catch (std::exception e) by value compiles but is wrong. It copies the exception, which slices a derived exception down to its base and loses the real message. Always catch by const reference β catch (const std::exception &e).
#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
#include <cmath>
/* a small validated class using the right exception types */
class Student {
std::string name;
int age;
std::vector<double> marks;
public:
Student(std::string n, int a) : name(std::move(n)), age(a) {
if (name.empty())
throw std::invalid_argument("name must not be empty");
if (age < 5 || age > 120)
throw std::out_of_range("age " + std::to_string(a)
+ " is implausible");
}
void addMark(double m) {
if (m < 0 || m > 100)
throw std::domain_error("mark must be 0..100");
if (marks.size() >= 8)
throw std::length_error("at most 8 subjects");
marks.push_back(m);
}
double average() const {
if (marks.empty())
throw std::runtime_error("no marks recorded yet");
double s = 0;
for (double m : marks) s += m;
return s / marks.size();
}
const std::string& getName() const { return name; }
};
int main() {
/* the happy path */
Student ok("Ram Bahadur", 22);
ok.addMark(87.5); ok.addMark(91.0); ok.addMark(76.5);
std::cout << " " << ok.getName() << " average = "
<< ok.average() << "\n";
/* each failure caught by its SPECIFIC type */
struct Case { const char *label; void (*fn)(); };
try { Student bad("", 20); }
catch (const std::invalid_argument &e) {
std::cout << " invalid_argument: " << e.what() << "\n";
}
try { Student bad("Sita", 200); }
catch (const std::out_of_range &e) {
std::cout << " out_of_range : " << e.what() << "\n";
}
try { ok.addMark(150); }
catch (const std::domain_error &e) {
std::cout << " domain_error : " << e.what() << "\n";
}
try { Student e("Hari", 20); e.average(); }
catch (const std::runtime_error &e) {
std::cout << " runtime_error : " << e.what() << "\n";
}
/* ONE handler for the whole family, via the base class */
for (int badAge : {3, 500}) {
try { Student s("Test", badAge); }
catch (const std::exception &e) {
std::cout << " base handler : " << e.what() << "\n";
}
}
/* library exceptions use the same hierarchy */
try {
std::vector<int> v{1,2,3};
std::cout << v.at(10);
}
catch (const std::out_of_range &e) {
std::cout << " vector::at threw: out_of_range\n";
}
try { std::string s; s.at(5); }
catch (const std::out_of_range&) {
std::cout << " string::at threw: out_of_range\n";
}
return 0;
}
Output:
Ram Bahadur average = 85
invalid_argument: name must not be empty
out_of_range : age 200 is implausible
domain_error : mark must be 0..100
runtime_error : no marks recorded yet
base handler : age 3 is implausible
base handler : age 500 is implausible
vector::at threw: out_of_range
string::at threw: out_of_range
#include <iostream>
#include <stdexcept>
#include <string>
/* ---- THE ERROR-CODE APPROACH ---- */
enum class Err { Ok, DivZero, Negative, TooBig };
Err safeDivide(double a, double b, double &out) {
if (b == 0) return Err::DivZero;
out = a / b;
return Err::Ok;
}
Err safeSqrt(double x, double &out) {
if (x < 0) return Err::Negative;
out = std::sqrt(x);
return Err::Ok;
}
/* the caller must check EVERY step β and can forget to */
Err computeCodes(double a, double b, double &result) {
double q;
Err e = safeDivide(a, b, q);
if (e != Err::Ok) return e; /* check 1 */
double r;
e = safeSqrt(q, r);
if (e != Err::Ok) return e; /* check 2 */
e = safeDivide(100, r, result);
if (e != Err::Ok) return e; /* check 3 */
return Err::Ok;
}
/* ---- THE EXCEPTION APPROACH ---- */
double divide(double a, double b) {
if (b == 0) throw std::domain_error("division by zero");
return a / b;
}
double root(double x) {
if (x < 0) throw std::domain_error("sqrt of a negative");
return std::sqrt(x);
}
/* the happy path is UNCLUTTERED β no checks at all */
double computeExc(double a, double b) {
return divide(100, root(divide(a, b)));
}
int main() {
double r;
/* error codes */
for (auto [a, b] : {std::pair<double,double>{50, 2},
{50, 0}, {-50, 2}}) {
Err e = computeCodes(a, b, r);
std::cout << " codes a=" << a << " b=" << b << " -> ";
switch (e) {
case Err::Ok: std::cout << r << "\n"; break;
case Err::DivZero: std::cout << "DivZero\n"; break;
case Err::Negative: std::cout << "Negative\n"; break;
default: std::cout << "other\n";
}
}
/* exceptions */
for (auto [a, b] : {std::pair<double,double>{50, 2},
{50, 0}, {-50, 2}}) {
std::cout << " exc a=" << a << " b=" << b << " -> ";
try { std::cout << computeExc(a, b) << "\n"; }
catch (const std::exception &e) {
std::cout << e.what() << "\n";
}
}
return 0;
}
Output:
codes a=50 b=2 -> 20
codes a=50 b=0 -> DivZero
codes a=-50 b=2 -> Negative
exc a=50 b=2 -> 20
exc a=50 b=0 -> division by zero
exc a=-50 b=2 -> sqrt of a negative
std::expected<T, E> returns either a value or an error in one object, with no throw cost and no way to accidentally ignore it β the same design as Rust's Result<T, E> and Haskell's Either. C++17's std::optional<T> already covers "a value or nothing". Meanwhile noexcept lets you promise a function never throws, which the compiler uses to generate better code β and std::vector checks it to decide whether moving elements during reallocation is safe. Search "std::expected vs exceptions" and "why noexcept matters for vector".try, throw, catch and describe stack unwinding β destructors of local objects run before the handler, which is a favourite trace question. State that execution resumes after the handler, never at the throw point, and that an unmatched exception calls std::terminate. Know the standard hierarchy (logic_error for bugs, runtime_error for external conditions, both under std::exception with what()). Two high-value details: no implicit conversions in matching, and always catch by const reference to avoid slicing.Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loadingβ¦