Programming Language & Its Applications β Generic Programming and Exception Handling, NEC licence examination syllabus (Nepal Engineering Council).
Declaring what a function may throw β the old throw() lists, why they failed, and noexcept which replaced them.
noexcept looks like documentation and is actually a performance switch. std::vector inspects whether your move constructor is noexcept to decide, during reallocation, whether it can move your elements or must copy them β because if a move could throw halfway through, the vector would be left in a broken state with no way to recover. So a missing noexcept on a move constructor silently turns every vector growth from pointer swaps into full deep copies. This is one of the few one-word changes that measurably alters real program speed. Search "noexcept move constructor vector reallocation".noexcept β the replacement#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
#include <type_traits>
int safeAdd(int a, int b) noexcept { return a + b; }
int mayThrow(int a) { if (a < 0)
throw std::domain_error("neg");
return a * 2; }
int* allocate(int n) { return new int[n]; }
/* conditional noexcept: this is noexcept only when T's
operations are */
template <typename T>
void mySwap(T &a, T &b)
noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_move_assignable_v<T>)
{
T t = std::move(a); a = std::move(b); b = std::move(t);
}
struct Throwy {
Throwy() = default;
Throwy(Throwy&&) { } /* NOT noexcept */
Throwy& operator=(Throwy&&) { return *this; }
};
int main() {
std::cout << std::boolalpha;
/* the noexcept OPERATOR queries at compile time */
std::cout << " noexcept(1 + 2) = "
<< noexcept(1 + 2) << "\n";
std::cout << " noexcept(safeAdd(1,2)) = "
<< noexcept(safeAdd(1,2)) << "\n";
std::cout << " noexcept(mayThrow(1)) = "
<< noexcept(mayThrow(1)) << "\n";
std::cout << " noexcept(allocate(10)) = "
<< noexcept(allocate(10)) << "\n";
std::cout << " noexcept(new int) = "
<< noexcept(new int) << "\n";
/* conditional noexcept resolves per type */
int i = 1, j = 2;
std::string s1 = "a", s2 = "b";
Throwy t1, t2;
std::cout << " mySwap<int> noexcept? "
<< noexcept(mySwap(i, j)) << "\n";
std::cout << " mySwap<string> noexcept? "
<< noexcept(mySwap(s1, s2)) << "\n";
std::cout << " mySwap<Throwy> noexcept? "
<< noexcept(mySwap(t1, t2)) << "\n";
/* type traits used by the standard library */
std::cout << " is_nothrow_move_constructible<string> = "
<< std::is_nothrow_move_constructible_v<std::string>
<< "\n";
std::cout << " is_nothrow_move_constructible<Throwy> = "
<< std::is_nothrow_move_constructible_v<Throwy>
<< "\n";
return 0;
}
Output:
noexcept(1 + 2) = true
noexcept(safeAdd(1,2)) = true
noexcept(mayThrow(1)) = false
noexcept(allocate(10)) = false
noexcept(new int) = false
mySwap<int> noexcept? true
mySwap<string> noexcept? true
mySwap<Throwy> noexcept? false
is_nothrow_move_constructible<string> = true
is_nothrow_move_constructible<Throwy> = false
noexcept is a declaration, not an analysis. The compiler never inspects your function body to decide. noexcept(mayThrow(1)) is false even for arguments that provably cannot throw, and conversely, marking a throwing function noexcept compiles fine β and terminates the program at runtime.
#include <iostream>
#include <vector>
#include <string>
#include <type_traits>
/* Two identical classes. The ONLY difference is one keyword
on the move constructor. */
struct Fast {
std::string data;
int id;
Fast(int i) : data(1000, 'x'), id(i) {}
Fast(Fast &&o) noexcept
: data(std::move(o.data)), id(o.id) { ++moves; }
Fast(const Fast &o) : data(o.data), id(o.id) { ++copies; }
Fast& operator=(Fast&&) noexcept = default;
Fast& operator=(const Fast&) = default;
static int moves, copies;
};
int Fast::moves = 0, Fast::copies = 0;
struct Slow {
std::string data;
int id;
Slow(int i) : data(1000, 'x'), id(i) {}
Slow(Slow &&o) /* NO noexcept */
: data(std::move(o.data)), id(o.id) { ++moves; }
Slow(const Slow &o) : data(o.data), id(o.id) { ++copies; }
static int moves, copies;
};
int Slow::moves = 0, Slow::copies = 0;
int main() {
std::cout << std::boolalpha;
std::cout << " Fast move is noexcept: "
<< std::is_nothrow_move_constructible_v<Fast> << "\n";
std::cout << " Slow move is noexcept: "
<< std::is_nothrow_move_constructible_v<Slow> << "\n\n";
/* force several reallocations by pushing without
reserving */
{
std::vector<Fast> v;
for (int i = 0; i < 20; i++) v.push_back(Fast(i));
std::cout << " Fast: " << Fast::moves << " moves, "
<< Fast::copies << " copies\n";
}
{
std::vector<Slow> v;
for (int i = 0; i < 20; i++) v.push_back(Slow(i));
std::cout << " Slow: " << Slow::moves << " moves, "
<< Slow::copies << " copies\n";
}
std::cout << "\n Each copy duplicates a 1000-char string;\n"
<< " each move just steals a pointer.\n";
return 0;
}
Output:
Fast move is noexcept: true
Slow move is noexcept: false
Fast: 51 moves, 0 copies
Slow: 20 moves, 31 copies
Each copy duplicates a 1000-char string;
each move just steals a pointer.
#include <iostream>
#include <stdexcept>
struct Loud {
~Loud() { std::cout << " ~Loud ran\n"; }
};
/* A LIE: this is marked noexcept but throws. It compiles
with only a warning. */
void liar() noexcept {
Loud l;
throw std::runtime_error("broke the promise");
}
int main() {
std::cout << " calling a noexcept function that throws:\n";
try {
liar();
}
catch (const std::exception &e) {
std::cout << " NEVER REACHED: " << e.what() << "\n";
}
std::cout << " also never reached\n";
return 0;
}
Output:
calling a noexcept function that throws:
terminate called after throwing an instance of 'std::runtime_error'
what(): broke the promise
Abort trap: 6
(g++ warning at compile time:
warning: 'throw' will always call 'terminate' [-Wterminate])
throw() lists is a genuinely instructive piece of language-design history, because Java made the opposite choice and does check its throws clauses at compile time. Two decades of experience produced a broad consensus that Java's checked exceptions were also a mistake β they force callers to write meaningless wrappers, and they break when interfaces are extended. C# deliberately omitted them; Kotlin removed them from Java. Search "checked exceptions considered harmful" and "why C++ removed exception specifications" β the two stories converge on the same conclusion from different directions.throw(TypeList)) were deprecated in C++11 and removed in C++17, and give the reasons β not compile-time checked, violation calls terminate, and they made code slower. Then contrast with noexcept: it lets the compiler omit unwind machinery rather than add a check. State that a violation calls std::terminate without unwinding, so destructors do not run. Know the two roles of the keyword (specifier and operator) and that move constructors should always be marked noexcept β with the vector reallocation reason as the strongest supporting detail.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β¦