COMP6771 Advanced C++ Programming
Week 4.2 Exceptions
1
Why?
In this lecture
Sometimes our programs need to deal with unexpected runtime errors and handle them gracefully.
What?
Exception object
Throwing and catching exceptions Rethrowing
noexcept
2
Let’s start with an example
What does this produce?
1 #include
2 #include
3
4 auto main() -> int {
5 std::cout << "Enter -1 to quit\n";
6 std::vector
7 std::cout << "Enter an index: ";
8 for (int print_index; std::cin >> print_index; ) {
9 if (print_index == -1) break;
10 std::cout << items.at(print_index) << '\n';
11 std::cout << "Enter an index: ";
12 }
13 }
demo455-exception1.cpp
3.1
Let's start with an example
What does this produce?
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
#include
#include
auto main() -> int {
std::cout << "Enter -1 to quit\n";
std::vector
std::cout << "Enter an index: ";
for (int print_index; std::cin >> print_index; ) {
if (print_index == -1) break;
try {
std::cout << items.at(print_index) << '\n';
items.resize(items.size() + 10);
} catch (const std::out_of_range& e) {
std::cout << "Index out of bounds\n";
} catch (...) {
std::cout << "Something else happened";
}
std::cout << "Enter an index: ";
}
}
demo455-exception2.cpp
3.2
Exceptions: What & Why?
What:
Exceptions: Are for exceptional circumstances
Happen during run-time anomalies (things not going to plan A!) Exception handling:
Run-time mechanism
C++ detects a run-time error and raises an appropriate exception Another unrelated part of code catches the exception, handles it, and potentially rethrows it
Why:
Allows us to gracefully and programmatically deal with anomalies, as opposed to our program crashing.
4
What are "Exception Objects"?
Any type we derive from std::exception
throw std::out_of_range("Exception!"); throw std::bad_alloc("Exception!");
Why std::exception? Why classes?
#include
#include
https://en.cppreference.com/w/cpp/error/exception https://stackoverflow.com/questions/25163105/stdexcept-vs-exception-headers-in-c
5
Conceptual Structure
Exceptions are treated like lvalues
Limited type conversions exist (pay attention to them):
nonconst to const
other conversions we will not cover in the course
1
2
3
4
5
6 7}
try {
// Code that may throw an exception
} catch (/* exception type */) {
// Do something with the exception
} catch (…) { // any exception
// Do something with the exception
https://en.cppreference.com/w/cpp/language/try_catch
6.1
Multiple catch options
This does not mean multiple catches will happen, but rather that multiple options are possible for a single catch
1 #include
2 #include
3
4 auto main() -> int {
5 auto items = std::vector
6 try{
7 items.resize(items.max_size() + 1);
8 } catch (std::bad_alloc& e) {
9 std::cout << "Out of bounds.\n";
10 } catch (std::exception&) {
11 std::cout << "General exception.\n";
12 }
13 }
6.2
Catching the right way
Throw by value, catch by const reference
Ways to catch exceptions:
By value (no!)
By pointer (no!) By reference (yes)
References are preferred because:
more efficient, less copying (exploring today)
no slicing problem (related to polymorphism, exploring later)
(Extra reading for those interested)
https://blog.knatten.org/2010/04/02/always-catch-exceptions- by-reference/
7.1
Catch by value is inefficient
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include
class Giraffe {
public:
Giraffe() { std::cout << "Giraffe constructed" << '\n'; }
Giraffe(const Giraffe &g) { std::cout << "Giraffe copy-constructed" << '\n'; }
~Giraffe() { std::cout << "Giraffe destructed" << '\n'; }
};
void zebra() {
throw Giraffe{};
}
void llama() { try{
zebra();
catch (Giraffe g) {
std::cout << "caught in llama; rethrow" << '\n';
throw;
} }
}
int try{
main() {
llama();
catch (Giraffe g) {
std::cout << "caught in main" << '\n';
} }
}
demo456-by-value.cpp
7.2
Catch by value inefficiency
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include
class Giraffe {
public:
Giraffe() { std::cout << "Giraffe constructed" << '\n'; }
Giraffe(const Giraffe &g) { std::cout << "Giraffe copy-constructed" << '\n'; }
~Giraffe() { std::cout << "Giraffe destructed" << '\n'; }
};
void zebra() {
throw Giraffe{};
}
void llama() { try{
zebra();
catch (const Giraffe& g) {
std::cout << "caught in llama; rethrow" << '\n';
throw;
} }
}
int try{
main() {
llama();
catch (const Giraffe& g) {
std::cout << "caught in main" << '\n';
} }
}
demo457-by-ref.cpp
7.3
Rethrow
When an exception is caught, by default the catch will be the only part of the code to use/action the exception
What if other catches (lower in the precedence order) want to do something with the thrown exception?
9
10
11
12
13
14
15
} catch (T& e2) {
std::cout << "Caught too!\n";
throw;
}
} catch (...) {
std::cout << "Caught too!!\n";
}
1
2
3
4
5
6
7 8}
try { try{
try {
throw T{};
} catch (T& e1) {
std::cout << "Caught\n";
throw;
8.1
(Not-advisable) Rethrow, catch by value
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include
class Cake {
public:
Cake() : pieces_{8} {}
int getPieces() { return pieces_; }
Cake& operator–() { –pieces_; return *this; }
private:
int pieces_;
};
int main() { try{
try { try {
}
}
catch (Cake e2) {
–e2;
std::cout << "e2 Pieces: " << e2.getPieces() << " addr: " << &e2 << "\n";
throw;
}
throw Cake{};
catch (Cake& e1) {
--e1;
std::cout << "e1 Pieces: " << e1.getPieces() << " addr: " << &e1 << "\n";
throw;
}
} catch (Cake& e3) {
--e3;
std::cout << "e3 Pieces: " << e3.getPieces() << " addr: " << &e3 << "\n";
}
}
demo458-rethrow.cpp
8.2
Exception safety levels
This part is not specific to C++
Operations performed have various levels of safety
No-throw (failure transparency)
Strong exception safety (commit-or-rollback) Weak exception safety (no-leak)
No exception safety
9.1
No-throw guarantee
Also known as failure transparency
Operations are guaranteed to succeed, even in exceptional circumstances
Exceptions may occur, but are handled internally
No exceptions are visible to the client
This is the same, for all intents and purposes, as noexcept in C++ Examples:
Closing a file
Freeing memory
Anything done in constructors or moves (usually)
Creating a trivial object on the stack (made up of only ints)
9.2
Strong exception safety
Also known as "commit or rollback" semantics
Operations can fail, but failed operations are guaranteed to have no visible effects
Probably the most common level of exception safety for types in C++ All your copy-constructors should generally follow these semantics Similar for copy-assignment
Copy-and-swap idiom (usually) follows these semantics (why?) Can be difficult when manually writing copy-assignment
9.3
Strong exception safety
To achieve strong exception safety, you need to:
First perform any operations that may throw, but don't do anything irreversible
Then perform any operations that are irreversible, but don't throw
9.4
Basic exception safety
This is known as the no-leak guarantee
Partial execution of failed operations can cause side effects, but:
All invariants must be preserved No resources are leaked
Any stored data will contain valid values, even if it was different now from before the exception
Does this sound familiar? A "valid, but unspecified state"
Move constructors that are not noexcept follow these semantics
9.5
No exception safety
No guarantees
Don't write C++ with no exception safety
Very hard to debug when things go wrong
Very easy to fix - wrap your resources and attach lifetimes
This gives you basic exception safety for free
9.6
noexcept specifier
Specifies whether a function could potentially throw
It doesn't not actually prevent a function from throwing an exception https://en.cppreference.com/w/cpp/language/noexcept_spec STL functions can operate more efficiently on noexcept functions
1 class S {
2 public:
3 int foo() const; // may throw
4}
5
6 class S {
7 public:
8 int foo() const noexcept; // does not throw 9}
10
CHECK_NOTHROW(expr);
CHECK_THROWS(expr);
Testing exceptions
CHECK_THROWS_AS(expr, type);
Checks expr doesn't throw an exception.
Checks expr throws an exception. Checks expr throws type (or
somthing derived from type). REQUIRES_THROWS* also available.
11 . 1
Testing exceptions
namespace Matchers = Catch::Matchers;
CHECK_THROWS_WITH(
expr,
Matchers::Message("message"));
CHECK_THROWS_MATCHES(
expr,
type,
Matchers::Message("message"));
Checks expr throws an exception with a message.
CHECK_THROWS_AS and CHECK_THROWS_WITH in a single check.
REQUIRES_THROWS* also available.
11 . 2