Programming Language & Its Applications β Generic Programming and Exception Handling, NEC licence examination syllabus (Nepal Engineering Council).
Catch it, do something local, then pass it on β throw; with no operand.
throw e; instead of throw;) silently destroys the exception's real type, which is one of the most quietly damaging bugs in C++. Search "throw vs throw e slicing".throw; versus throw e;#include <iostream>
#include <stdexcept>
#include <string>
/* a custom exception carrying EXTRA data */
class DiskError : public std::runtime_error {
int errcode;
public:
DiskError(const std::string &m, int c)
: std::runtime_error(m), errcode(c) {}
int code() const { return errcode; }
};
void lowLevel() { throw DiskError("sector read failed", 27); }
/* CORRECT: bare throw preserves the DiskError type */
void middleGood() {
try { lowLevel(); }
catch (const std::exception &e) {
std::cout << " [middleGood] logging: " << e.what() << "\n";
throw; /* rethrow, unchanged */
}
}
/* WRONG: throw e; slices DiskError down to std::exception */
void middleBad() {
try { lowLevel(); }
catch (const std::exception &e) {
std::cout << " [middleBad] logging: " << e.what() << "\n";
throw e; /* COPY β type lost */
}
}
int main() {
std::cout << " --- with bare throw; ---\n";
try { middleGood(); }
catch (const DiskError &e) {
std::cout << " caught DiskError, code=" << e.code()
<< " msg=" << e.what() << "\n";
}
catch (const std::exception &e) {
std::cout << " caught only std::exception: "
<< e.what() << "\n";
}
std::cout << " --- with throw e; ---\n";
try { middleBad(); }
catch (const DiskError &e) {
std::cout << " caught DiskError, code=" << e.code() << "\n";
}
catch (const std::exception &e) {
std::cout << " caught only std::exception: "
<< e.what() << " <-- DiskError data LOST\n";
}
return 0;
}
Output:
--- with bare throw; ---
[middleGood] logging: sector read failed
caught DiskError, code=27 msg=sector read failed
--- with throw e; ---
[middleBad] logging: sector read failed
caught only std::exception: std::exception <-- DiskError data LOST
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
/* ---- layer 1: the resource, which is always released ---- */
class Connection {
std::string host;
bool open;
public:
Connection(std::string h) : host(std::move(h)), open(true) {
std::cout << " + connected to " << host << "\n";
}
~Connection() {
if (open) std::cout << " - closed " << host << "\n";
}
void query(const std::string &q) {
if (q.find("DROP") != std::string::npos)
throw std::invalid_argument("refusing DROP: " + q);
if (host == "db-down")
throw std::runtime_error("connection reset by peer");
std::cout << " query ok: " << q << "\n";
}
};
/* ---- layer 2: the driver. Adds context, RETHROWS. ---- */
void runQuery(const std::string &host, const std::string &q) {
Connection c(host);
try {
c.query(q);
}
catch (const std::exception &e) {
/* local work: record what happened HERE */
std::cout << " [driver] on host " << host
<< ": " << e.what() << "\n";
throw; /* the driver cannot decide policy β pass up */
}
/* ~Connection runs on BOTH paths */
}
/* ---- layer 3: the application. Decides what to DO. ---- */
int main() {
struct Job { const char *host; const char *sql; };
std::vector<Job> jobs = {
{"db-primary", "SELECT * FROM students"},
{"db-primary", "DROP TABLE students"},
{"db-down", "SELECT count(*)"},
};
for (const auto &j : jobs) {
std::cout << " job: " << j.sql << " @ " << j.host << "\n";
try {
runQuery(j.host, j.sql);
std::cout << " result: success\n";
}
catch (const std::invalid_argument &e) {
/* policy: a bad query is OUR bug, do not retry */
std::cout << " result: rejected, not retrying\n";
}
catch (const std::runtime_error &e) {
/* policy: a transport failure β retry elsewhere */
std::cout << " result: transient, retrying on "
<< "db-replica\n";
try { runQuery("db-replica", j.sql); }
catch (const std::exception &e2) {
std::cout << " retry also failed\n";
}
}
}
return 0;
}
Output:
job: SELECT * FROM students @ db-primary
+ connected to db-primary
query ok: SELECT * FROM students
- closed db-primary
result: success
job: DROP TABLE students @ db-primary
+ connected to db-primary
[driver] on host db-primary: refusing DROP: DROP TABLE students
- closed db-primary
result: rejected, not retrying
job: SELECT count(*) @ db-down
+ connected to db-down
[driver] on host db-down: connection reset by peer
- closed db-down
result: transient, retrying on db-replica
+ connected to db-replica
query ok: SELECT count(*)
- closed db-replica
result: success
#include <iostream>
#include <stdexcept>
#include <string>
class Resource {
std::string name;
public:
Resource(std::string n, bool fail) : name(std::move(n)) {
if (fail) throw std::runtime_error("cannot acquire " + name);
std::cout << " + " << name << "\n";
}
~Resource() { std::cout << " - " << name << "\n"; }
};
class Widget {
Resource a, b;
public:
Widget(bool failA, bool failB)
try : a("resA", failA), b("resB", failB) {
std::cout << " Widget body\n";
}
catch (const std::exception &e) {
std::cout << " [ctor handler] " << e.what() << "\n";
/* an implicit throw; happens here even if we
write nothing β a half-built object cannot be
returned */
throw std::runtime_error(std::string("Widget failed: ")
+ e.what());
}
~Widget() { std::cout << " ~Widget\n"; }
};
int main() {
std::cout << " case 1: both succeed\n";
{ Widget w(false, false); }
std::cout << " case 2: second resource fails\n";
try { Widget w(false, true); }
catch (const std::exception &e) {
std::cout << " main caught: " << e.what() << "\n";
}
std::cout << " case 3: first resource fails\n";
try { Widget w(true, false); }
catch (const std::exception &e) {
std::cout << " main caught: " << e.what() << "\n";
}
return 0;
}
Output:
case 1: both succeed
+ resA
+ resB
Widget body
~Widget
- resB
- resA
case 2: second resource fails
+ resA
- resA
[ctor handler] cannot acquire resB
main caught: Widget failed: cannot acquire resB
case 3: first resource fails
[ctor handler] cannot acquire resA
main caught: Widget failed: cannot acquire resA
std::exception_ptr lets you store an exception and rethrow it elsewhere β even on a different thread. That is exactly how std::future works: an exception thrown inside std::async is captured, carried across the thread boundary, and rethrown when you call future.get(). Without it, an exception on a worker thread would just terminate the program. std::current_exception() captures, std::rethrow_exception(p) replays. Search "std::exception_ptr across threads" β it is the mechanism behind every concurrent error report in modern C++.throw; versus throw e; distinction is the core question β a bare throw rethrows the original object preserving its dynamic type, while throw e; throws a sliced copy and loses derived data. Be ready to show a program where the outer specific handler stops matching. State that a bare throw outside any catch calls std::terminate. Know the log-and-rethrow pattern: catch where you have information, handle where you have responsibility. The constructor function-try-block rule (the handler must exit by throwing) is a good advanced point.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β¦