Programming Language & Its Applications β Generic Programming and Exception Handling, NEC licence examination syllabus (Nepal Engineering Council).
What happens when nothing catches β std::terminate, set_terminate, and the last chance to leave a diagnostic behind.
main. Search "Google Breakpad crash reporting".std::terminate is called#include <iostream>
#include <exception>
#include <stdexcept>
#include <cstdlib>
#include <string>
/* A diagnostic handler. It runs when nothing caught the
exception, and it must NOT return. */
void crashReporter() {
std::cerr << "\n ===== CRASH REPORT =====\n";
/* recover the in-flight exception, if any (C++11) */
if (std::exception_ptr p = std::current_exception()) {
try { std::rethrow_exception(p); }
catch (const std::exception &e) {
std::cerr << " type : std::exception subclass\n"
<< " what() : " << e.what() << "\n";
}
catch (int n) {
std::cerr << " type : int\n value : " << n << "\n";
}
catch (...) {
std::cerr << " type : unrecognised\n";
}
} else {
std::cerr << " no exception in flight "
"(terminate called directly)\n";
}
std::cerr << " action : flushing logs, aborting\n"
<< " ========================\n";
std::cerr.flush();
std::abort(); /* must not return */
}
struct Tracked {
std::string name;
Tracked(std::string n) : name(std::move(n)) {
std::cout << " +" << name << "\n";
}
~Tracked() { std::cout << " -" << name << "\n"; }
};
void deep() {
Tracked t("deep-local");
throw std::out_of_range("index 42 out of range");
}
int main() {
std::set_terminate(crashReporter);
std::cout << " --- caught properly: destructors run ---\n";
try { deep(); }
catch (const std::exception &e) {
std::cout << " caught: " << e.what() << "\n";
}
std::cout << " --- now UNCAUGHT ---\n";
Tracked outer("main-local");
deep(); /* nothing catches this */
std::cout << " unreachable\n";
}
Output:
--- caught properly: destructors run ---
+deep-local
-deep-local
caught: index 42 out of range
--- now UNCAUGHT ---
+main-local
+deep-local
===== CRASH REPORT =====
type : std::exception subclass
what() : index 42 out of range
action : flushing logs, aborting
========================
Abort trap: 6
try/catch in main is worth writing even when it only logs and returns. Catching means the stack is unwound, so files flush and locks release β a terminate handler can report the problem but cannot undo the fact that cleanup was skipped.
main#include <iostream>
#include <exception>
#include <stdexcept>
#include <string>
#include <vector>
#include <cstdlib>
/* exit codes a shell script can act on */
enum ExitCode {
OK = 0, BAD_INPUT = 2, IO_FAILURE = 3,
OUT_OF_MEMORY = 4, INTERNAL_BUG = 70, UNKNOWN = 99
};
void lastResort() {
std::cerr << " [terminate] an exception escaped every "
"handler\n";
if (auto p = std::current_exception()) {
try { std::rethrow_exception(p); }
catch (const std::exception &e) {
std::cerr << " [terminate] " << e.what() << "\n";
}
catch (...) { std::cerr << " [terminate] non-standard type\n"; }
}
std::cerr.flush();
std::_Exit(UNKNOWN); /* no destructors, no atexit */
}
void runJob(int job) {
switch (job) {
case 0: std::cout << " job 0 completed\n"; break;
case 1: throw std::invalid_argument("empty student name");
case 2: throw std::ios_base::failure("cannot open marks.dat");
case 3: throw std::bad_alloc();
case 4: throw std::logic_error("impossible branch reached");
case 5: throw std::string("a non-standard exception type");
}
}
/* THE STANDARD ROBUST MAIN: one try, ordered handlers,
a meaningful exit code for each class of failure */
int main() {
std::set_terminate(lastResort);
for (int job = 0; job <= 5; job++) {
std::cout << " job " << job << ":\n";
try {
runJob(job);
std::cout << " exit code would be " << OK << "\n";
}
catch (const std::invalid_argument &e) {
std::cerr << " input error: " << e.what()
<< " -> exit " << BAD_INPUT << "\n";
}
catch (const std::ios_base::failure &e) {
std::cerr << " I/O error: " << e.what()
<< " -> exit " << IO_FAILURE << "\n";
}
catch (const std::bad_alloc &) {
std::cerr << " out of memory -> exit "
<< OUT_OF_MEMORY << "\n";
}
catch (const std::logic_error &e) {
std::cerr << " INTERNAL BUG: " << e.what()
<< " -> exit " << INTERNAL_BUG << "\n";
}
catch (const std::exception &e) {
std::cerr << " unclassified: " << e.what()
<< " -> exit " << UNKNOWN << "\n";
}
catch (...) {
std::cerr << " non-standard exception -> exit "
<< UNKNOWN << "\n";
}
}
std::cout << " all jobs attempted; nothing escaped\n";
return OK;
}
Output:
job 0:
job 0 completed
exit code would be 0
job 1:
input error: empty student name -> exit 2
job 2:
I/O error: cannot open marks.dat -> exit 3
job 3:
out of memory -> exit 4
job 4:
INTERNAL BUG: impossible branch reached -> exit 70
job 5:
non-standard exception -> exit 99
all jobs attempted; nothing escaped
#include <iostream>
#include <stdexcept>
#include <exception>
/* A destructor that throws DURING unwinding = terminate */
struct Dangerous {
~Dangerous() noexcept(false) {
std::cout << " ~Dangerous throwing\n";
throw std::runtime_error("from a destructor");
}
};
/* THE CORRECT PATTERN: a destructor swallows its own
exceptions rather than letting them escape */
struct Safe {
~Safe() {
try {
throw std::runtime_error("internal cleanup failure");
}
catch (...) {
std::cout << " ~Safe swallowed its own error\n";
}
}
};
int main() {
std::cout << " 1. Safe destructor during unwinding:\n";
try {
Safe s;
throw std::logic_error("the original exception");
}
catch (const std::exception &e) {
std::cout << " caught: " << e.what() << "\n";
}
std::cout << " 2. uncaught_exceptions() lets a destructor "
"detect unwinding:\n";
std::cout << " outside any throw: "
<< std::uncaught_exceptions() << "\n";
try {
struct Probe {
~Probe() {
std::cout << " inside unwinding: "
<< std::uncaught_exceptions() << "\n";
}
} p;
throw std::runtime_error("probe");
} catch (...) { }
std::cout << " 3. a throwing destructor during unwinding "
"would call terminate\n";
/* try { Dangerous d; throw std::logic_error("first"); }
catch (...) { }
β terminate called: two exceptions in flight */
std::cout << " (commented out β it aborts the program)\n";
return 0;
}
Output:
1. Safe destructor during unwinding:
~Safe swallowed its own error
caught: the original exception
2. uncaught_exceptions() lets a destructor detect unwinding:
outside any throw: 0
inside unwinding: 1
3. a throwing destructor during unwinding would call terminate
(commented out β it aborts the program)
<stacktrace> so you can capture std::stacktrace::current() inside the handler and print the actual call chain β something that previously required platform-specific code (backtrace() on Linux, CaptureStackBackTrace on Windows) or a library like Boost.Stacktrace. Combined with the crash reporter above, that turns "uncaught std::out_of_range" into a filename and line number. Search "C++23 stacktrace library" and "Boost.Stacktrace terminate handler".std::terminate β no matching handler, an exception escaping a noexcept function, a destructor throwing during unwinding, a thread function letting one escape, and a bare throw; with nothing in flight. The highest-value point is that terminate may not unwind the stack, so destructors do not run β which is why a top-level catch in main matters. Know std::set_terminate and that the handler must not return. Explain the two-exceptions rule as the reason destructors must not throw, and mention that std::unexpected was removed in C++17 along with dynamic exception specifications.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β¦