Programming Language & Its Applications β Generic Programming and Exception Handling, NEC licence examination syllabus (Nepal Engineering Council).
catch (...) β the safety net that catches anything, and the reasons to use it sparingly.
catch (...) guards every boundary where an exception must not escape. main uses it so a crash produces a log entry instead of a bare "Aborted". A thread function uses it because an exception leaving a thread terminates the whole process. A C API callback uses it because C has no exceptions and unwinding through C code is undefined. Every plugin host wraps plugin calls in it, so one badly-written plugin cannot take down the application. But used casually inside ordinary code it becomes the worst pattern in error handling β the silent swallow. Search "exception swallowing anti-pattern".#include <iostream>
#include <stdexcept>
#include <string>
#include <exception>
class CustomError {
int code;
public:
explicit CustomError(int c) : code(c) {}
int getCode() const { return code; }
};
void thrower(int which) {
switch (which) {
case 1: throw std::runtime_error("a standard error");
case 2: throw 42;
case 3: throw std::string("a bare string");
case 4: throw CustomError(99);
case 5: throw 3.14;
default: return;
}
}
/* naive catch-all: knows nothing about what happened */
void naive(int which) {
std::cout << " naive case " << which << ": ";
try { thrower(which); std::cout << "no throw\n"; }
catch (...) { std::cout << "something failed (no detail)\n"; }
}
/* the rethrow-and-recatch idiom recovers the type */
void smart(int which) {
std::cout << " smart case " << which << ": ";
try { thrower(which); std::cout << "no throw\n"; }
catch (...) {
try { throw; } /* rethrow INSIDE the handler */
catch (const std::exception &e) {
std::cout << "std::exception: " << e.what() << "\n";
}
catch (const CustomError &e) {
std::cout << "CustomError code " << e.getCode() << "\n";
}
catch (int n) {
std::cout << "int " << n << "\n";
}
catch (const std::string &s) {
std::cout << "std::string \"" << s << "\"\n";
}
catch (...) {
std::cout << "genuinely unknown type\n";
}
}
}
int main() {
for (int i = 1; i <= 6; i++) naive(i);
std::cout << "\n";
for (int i = 1; i <= 6; i++) smart(i);
return 0;
}
Output:
naive case 1: something failed (no detail)
naive case 2: something failed (no detail)
naive case 3: something failed (no detail)
naive case 4: something failed (no detail)
naive case 5: something failed (no detail)
naive case 6: no throw
smart case 1: std::exception: a standard error
smart case 2: int 42
smart case 3: std::string "a bare string"
smart case 4: CustomError code 99
smart case 5: genuinely unknown type
smart case 6: no throw
catch (...) { } looks like from the outside β except worse, because with a swallow there is no line at all. When you cannot name what went wrong, you cannot fix it, and you certainly cannot decide whether it was safe to continue.
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <exception>
/* ---- USE 1: cleanup and rethrow (pre-RAII style) ---- */
void legacyCleanup() {
int *buffer = new int[100];
try {
buffer[0] = 1;
throw std::runtime_error("mid-operation failure");
}
catch (...) {
delete[] buffer; /* release, then pass it on */
std::cout << " buffer freed by catch(...)\n";
throw;
}
delete[] buffer;
}
/* the modern equivalent needs no catch at all */
void modernCleanup() {
std::vector<int> buffer(100); /* RAII */
buffer[0] = 1;
throw std::runtime_error("mid-operation failure");
/* ~vector frees it during unwinding, automatically */
}
/* ---- USE 2: a boundary that must not leak exceptions ---- */
/* Simulating a C callback: unwinding through C code is
undefined behaviour, so nothing may escape. */
extern "C" int c_callback(int v) noexcept {
try {
if (v < 0) throw std::domain_error("negative input");
return v * 2;
}
catch (const std::exception &e) {
std::cout << " [callback] " << e.what() << "\n";
return -1; /* an error CODE */
}
catch (...) {
std::cout << " [callback] unknown failure\n";
return -1;
}
}
/* ---- USE 3: top-level in main ---- */
int runApplication(int mode) {
if (mode == 1) throw std::runtime_error("config missing");
if (mode == 2) throw 12345;
std::cout << " application ran normally\n";
return 0;
}
int main() {
std::cout << " USE 1 β cleanup and rethrow:\n";
try { legacyCleanup(); }
catch (const std::exception &e) {
std::cout << " caught after cleanup: " << e.what() << "\n";
}
try { modernCleanup(); }
catch (const std::exception &e) {
std::cout << " modern version, no catch needed: "
<< e.what() << "\n";
}
std::cout << " USE 2 β C boundary:\n";
std::cout << " c_callback(21) = " << c_callback(21) << "\n";
std::cout << " c_callback(-5) = " << c_callback(-5) << "\n";
std::cout << " USE 3 β top level:\n";
for (int mode = 0; mode <= 2; mode++) {
try { runApplication(mode); }
catch (const std::exception &e) {
std::cout << " FATAL: " << e.what() << "\n";
}
catch (...) {
std::cout << " FATAL: unknown exception type\n";
}
}
return 0;
}
Output:
USE 1 β cleanup and rethrow:
buffer freed by catch(...)
caught after cleanup: mid-operation failure
modern version, no catch needed: mid-operation failure
USE 2 β C boundary:
c_callback(21) = 42
[callback] negative input
c_callback(-5) = -1
USE 3 β top level:
application ran normally
FATAL: config missing
FATAL: unknown exception type
set_terminate and what happens with no handler#include <iostream>
#include <exception>
#include <stdexcept>
#include <cstdlib>
/* std::set_terminate installs a last-chance handler that
runs when an exception is never caught. It CANNOT resume β
it must end the program. */
void myTerminate() {
std::cout << " [terminate handler] uncaught exception\n";
/* recover the exception, if there is one (C++11) */
if (auto p = std::current_exception()) {
try { std::rethrow_exception(p); }
catch (const std::exception &e) {
std::cout << " [terminate handler] it was: "
<< e.what() << "\n";
}
catch (...) {
std::cout << " [terminate handler] unknown type\n";
}
}
std::cout << " [terminate handler] aborting cleanly\n";
std::abort();
}
struct Noisy {
~Noisy() { std::cout << " ~Noisy ran\n"; }
};
int main() {
std::set_terminate(myTerminate);
std::cout << " handled case:\n";
try { throw std::runtime_error("this one is caught"); }
catch (const std::exception &e) {
std::cout << " caught: " << e.what() << "\n";
}
std::cout << " now throwing with NO handler:\n";
Noisy n; /* will its destructor run? */
throw std::logic_error("nobody catches this");
/* unreachable */
}
Output:
handled case:
caught: this one is caught
now throwing with NO handler:
[terminate handler] uncaught exception
[terminate handler] it was: nobody catches this
[terminate handler] aborting cleanly
Abort trap: 6
noexcept is the inverse of this topic β instead of catching everything, you promise nothing escapes. It matters for more than documentation: std::vector inspects noexcept on your move constructor to decide whether reallocation can move elements or must copy them, so a missing noexcept can silently halve your performance. And violating the promise calls std::terminate immediately, with no unwinding. Search "noexcept move constructor vector performance" and "when to use noexcept".catch (...) matches any type, must be last, and gives no access to the exception object. Know the rethrow-and-recatch idiom (try { throw; } catch (const std::exception &e) {...}) as the way to recover the type inside a catch-all. Name the legitimate uses β cleanup-and-rethrow, a C/thread/destructor boundary, and top-level logging in main β and say plainly that an empty catch (...) { } is an anti-pattern. The high-value detail: an uncaught exception may call std::terminate without unwinding, so destructors do not run.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β¦