431 lines
No EOL
12 KiB
Markdown
431 lines
No EOL
12 KiB
Markdown
# Exceptions
|
||
___
|
||
|
||
## Throw
|
||
|
||
If you want to throw an exception you can simply call `throw`
|
||
|
||
```C++
|
||
void foo() {
|
||
throw "Error";
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
If you want to catch an error you need to use `try-catch`
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
void bar() {
|
||
try {
|
||
foo();
|
||
} catch (const std::exception& err) {
|
||
std::cout << err.what() << '\n';
|
||
}
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
___
|
||
|
||
## What happens during throw?
|
||
|
||
Programm will stop executing the next line, when reach `throw` command. It will startup **unwind** a stack untill it reach the first `try-catch` block which will catch this exception. Because the stack is unwinded all variables which go out of scope will be properly destructed.
|
||
|
||
<div class="multicolumn">
|
||
<div class="column">
|
||
|
||
```C++
|
||
struct Foo {
|
||
explicit Foo(int id): id(id) { std::cout << "C'tor id: " << id << "\n"; }
|
||
~Foo() { std::cout << "D'tor id: " << id << "\n"; }
|
||
|
||
int id;
|
||
};
|
||
|
||
void fun2() {
|
||
Foo foo(2);
|
||
std::cout << "This will print!\n";
|
||
throw std::runtime_error("Bad!");
|
||
std::cout << "This will not print!\n";
|
||
}
|
||
|
||
void fun1() {
|
||
std::cout << "We start here!\n";
|
||
Foo foo(1);
|
||
std::cout << "After construction of Foo we call function fun2 which throw\n";
|
||
fun2();
|
||
std::cout << "This will not print!\n";
|
||
}
|
||
|
||
int main() {
|
||
try {
|
||
fun1();
|
||
std::cout << "This will not print!\n";
|
||
} catch (const std::exception& err) {
|
||
std::cout << "exception: " << err.what() << '\n';
|
||
}
|
||
std::cout << "Exit\n";
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
</div>
|
||
<div class="column">
|
||
|
||
```bash
|
||
We start here!
|
||
C'tor id: 1
|
||
After construction of Foo we call function fun2 which throw
|
||
C'tor id: 2
|
||
This will print!
|
||
D'tor id: 2
|
||
D'tor id: 1
|
||
exception: Bad!
|
||
Exit
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
</div>
|
||
</div>
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
___
|
||
|
||
## Hierarchy
|
||
|
||
There are more than 30 exceptions in C++! Each exception could be a base class of other (see <a href="https://en.cppreference.com/w/cpp/error/exception">cppreference</a>). The most common are:
|
||
|
||
* <!-- .element: class="fragment fade-in" --> logic_error
|
||
* <!-- .element: class="fragment fade-in" --> out_of_range
|
||
* <!-- .element: class="fragment fade-in" --> bad_optional_access
|
||
* <!-- .element: class="fragment fade-in" --> runtime_error
|
||
* <!-- .element: class="fragment fade-in" --> bad_weak_ptr
|
||
* <!-- .element: class="fragment fade-in" --> bad_alloc
|
||
___
|
||
|
||
## How to properly catch an error
|
||
|
||
```C++
|
||
void doSth() {
|
||
std::cout << "Hello! ";
|
||
throw std::invalid_argument("SthBad!");
|
||
}
|
||
|
||
int main() {
|
||
try {
|
||
doSth();
|
||
} catch (const std::invalid_argument& err) {
|
||
std::cout << "invalid_argument: " << err.what() << '\n';
|
||
} catch (const std::logic_error& err) {
|
||
std::cout << "logic_error: " << err.what() << '\n';
|
||
} catch (const std::exception& err) {
|
||
std::cout << "exception: " << err.what() << '\n';
|
||
} catch (...) {
|
||
std::cout << "Undefined error!\n";
|
||
}
|
||
}
|
||
```
|
||
Output: `Hello! invalid_argument: SthBad!`.
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.85em" -->
|
||
|
||
___
|
||
|
||
What happens when I will try to catch an error with an unordered hierarchy?
|
||
|
||
```C++
|
||
void doSth() {
|
||
std::cout << "Hello! ";
|
||
throw std::invalid_argument("SthBad!");
|
||
}
|
||
|
||
int main() {
|
||
try {
|
||
doSth();
|
||
} catch (const std::logic_error& err) {
|
||
std::cout << "logic_error: " << err.what() << '\n';
|
||
} catch (const std::invalid_argument& err) {
|
||
std::cout << "invalid_argument: " << err.what() << '\n';
|
||
} catch (...) {
|
||
std::cout << "Undefined error!\n";
|
||
}
|
||
}
|
||
```
|
||
|
||
Output: `Hello! logic_error: SthBad!`
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```bash
|
||
main.cpp: In function ‘int main()’:
|
||
main.cpp:21:7: warning: exception of type ‘std::invalid_argument’ will be caught
|
||
21 | } catch (const std::invalid_argument& err) {
|
||
| ^~~~~
|
||
main.cpp:19:7: warning: by earlier handler for ‘std::logic_error’
|
||
19 | } catch (const std::logic_error& err) {
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.82em" -->
|
||
___
|
||
|
||
You can quickly create your error class by inheriting from the base class `std::exception`. We need to `override` one method which will return a proper error message: `virtual const char* what() const noexcept`
|
||
|
||
```C++
|
||
class MyError : public std::exception {
|
||
public:
|
||
explicit MyError(const std::string& what): _what(what) {}
|
||
explicit MyError(const char* what): MyError(std::string(what)) {}
|
||
explicit MyError(std::string_view what): MyError(std::string(what)) {}
|
||
const char* what() const noexcept override { return _what.c_str(); }
|
||
private:
|
||
std::string _what;
|
||
};
|
||
|
||
class OtherError : public MyError {
|
||
public:
|
||
using MyError::MyError;
|
||
};
|
||
|
||
void doSth() {
|
||
throw OtherError("SthBad!");
|
||
}
|
||
|
||
int main() {
|
||
try {
|
||
doSth();
|
||
} catch (const OtherError& err) {
|
||
std::cout << "OtherError: " << err.what() << '\n';
|
||
} catch (const MyError& err) {
|
||
std::cout << "MyError: " << err.what() << '\n';
|
||
} catch (const std::exception& err) {
|
||
std::cout << "exception: " << err.what() << '\n';
|
||
}
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.64em" -->
|
||
___
|
||
|
||
It's important to use your error classes instead of those defined by the standard library. It will allow you to determine if the error comes from the standard library or your functions.
|
||
|
||
```C++
|
||
class MyError : public std::exception {
|
||
public:
|
||
explicit MyError(const std::string& what): _what(what) {}
|
||
const char* what() const noexcept override { return _what.c_str(); }
|
||
private:
|
||
std::string _what;
|
||
};
|
||
|
||
class Foo {
|
||
public:
|
||
const std::string& getProcessName(uint64_t pid) { return _processes.at(pid); }
|
||
void addProcess(uint64_t pid, const std::string& processName) {
|
||
if (_processes.contains(pid)) {
|
||
throw MyError(std::format("Process with PID {} already exist", pid));
|
||
}
|
||
_processes[pid] = processName;
|
||
}
|
||
private:
|
||
std::map<uint64_t, std::string> _processes;
|
||
};
|
||
|
||
int main() {
|
||
try {
|
||
Foo foo;
|
||
const auto& processName = foo.getProcessName(20);
|
||
foo.addProcess(1234, "MySuperprocess!");
|
||
} catch (const MyError& err) {
|
||
std::cout << "MyError: " << err.what() << '\n';
|
||
} catch (const std::exception& err) {
|
||
std::cout << "exception: " << err.what() << '\n';
|
||
}
|
||
}
|
||
```
|
||
<!-- .slide: style="font-size: 0.60em" -->
|
||
___
|
||
|
||
## Ways to signal an error
|
||
|
||
* <!-- .element: class="fragment fade-in" --> throw an exception
|
||
* <!-- .element: class="fragment fade-in" --> return <code>enum class ErrorCode</code>
|
||
* <!-- .element: class="fragment fade-in" --> return nullptr
|
||
* <!-- .element: class="fragment fade-in" --> return false
|
||
* <!-- .element: class="fragment fade-in" --> return std::optional (C++17)
|
||
* <!-- .element: class="fragment fade-in" --> return std::expected (C++23)
|
||
___
|
||
|
||
## Descriptive errors
|
||
|
||
We have three ways to strictly describe what happens wrong: `exception`, `enum class`, and `std::expected`. Using these types we can say what exactly goes wrong, like: providing the wrong credentials, or we can't find some element, etc...
|
||
|
||
<div class="multicolumn">
|
||
<div class="column">
|
||
|
||
```C++
|
||
int foo(const std::string& num) {
|
||
if (num != "42") {
|
||
throw std::runtime_error("Number is different than 42!");
|
||
}
|
||
|
||
return 42;
|
||
}
|
||
|
||
enum class StatusCode {
|
||
Ok,
|
||
Not42Number
|
||
};
|
||
|
||
StatusCode foo2(const std::string& num, int& res) {
|
||
if (num != "42") {
|
||
return StatusCode::Not42Number;
|
||
}
|
||
|
||
res = 42;
|
||
return StatusCode::Ok;
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
</div>
|
||
<div class="column">
|
||
|
||
```C++
|
||
enum class ParserError {
|
||
invalid_input,
|
||
overflow
|
||
};
|
||
|
||
std::expected<double, ParserError> parse(std::string_view str) {
|
||
if (str.size() > 1'000) {
|
||
return std::unexpected(ParserError::overflow);
|
||
} else if (str.empty()) {
|
||
return std::unexpected(ParserError::invalid_input);
|
||
}
|
||
|
||
return 42;
|
||
}
|
||
|
||
int main() {
|
||
if (const auto num = parse("something")) {
|
||
std::cout << "value: " << *num << '\n';
|
||
} else if (num.error() == ParserError::overflow) {
|
||
std::cout << "error: overflow\n";
|
||
} else if (num.error() == ParserError::invalid_input) {
|
||
std::cout << "invalid_input!\n";
|
||
}
|
||
}
|
||
```
|
||
</div>
|
||
<!-- .element: class="fragment fade-in" -->
|
||
</div>
|
||
<!-- .slide: style="font-size: 0.64em" -->
|
||
___
|
||
|
||
## Non descriptive errors
|
||
|
||
There are also three ways how to inform about the error but without describing why. This is useful for simple functions that may have only one error like whether there is a value inside the map or not.
|
||
|
||
```C++
|
||
std::unique_ptr<int> foo3(const std::string& num) {
|
||
if (num != "42") {
|
||
return nullptr;
|
||
}
|
||
|
||
return std::make_unique<int>(42);
|
||
}
|
||
|
||
bool foo4(const std::string& num, int& res) {
|
||
if (num != "42") {
|
||
return false;
|
||
}
|
||
|
||
res = 42;
|
||
return true;
|
||
}
|
||
|
||
std::optional<int> foo5(const std::string& num) {
|
||
if (num != "42") {
|
||
return std::nullopt;
|
||
}
|
||
|
||
return 42;
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.7em" -->
|
||
___
|
||
|
||
### std::optional - more info
|
||
|
||
Introduced in c++17 `std::optional` is a useful class that may have a value or not.
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Doesn't allocate data on heap!
|
||
* <!-- .element: class="fragment fade-in" --> Require additional storage info -> bigger size
|
||
* <!-- .element: class="fragment fade-in" --> Not as efficient as normal integer -> need to perform additional actions
|
||
|
||
```C++
|
||
class Foo {
|
||
int a;
|
||
int b;
|
||
int c;
|
||
int d;
|
||
};
|
||
|
||
int main() {
|
||
std::cout << sizeof(int) << '\n';
|
||
std::cout << sizeof(std::optional<int>) << '\n';
|
||
std::cout << sizeof(Foo) << '\n';
|
||
std::cout << sizeof(std::optional<Foo>) << '\n';
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
4
|
||
8
|
||
16
|
||
20
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.8em" -->
|
||
___
|
||
|
||
### std::expected - more info
|
||
|
||
Introduced in c++23 `std::expected` is a useful class that may store one of two values:
|
||
* <!-- .element: class="fragment fade-in" --> expected -> the return value which we expect
|
||
* <!-- .element: class="fragment fade-in" --> unexpected -> the value that will be returned when something goes wrong
|
||
|
||
In most cases as an unexpected value, we return an enum code with a description of what went wrong, but we can return anything (except reference)
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```C++
|
||
struct Foo {
|
||
void print() const { std::cout << "Sorry your function doesn't work!"; }
|
||
};
|
||
|
||
std::expected<std::string, Foo> parse(std::string_view str) {
|
||
if (str.empty()) {
|
||
return std::unexpected(Foo{});
|
||
}
|
||
return "42";
|
||
}
|
||
|
||
int main() {
|
||
if (const auto num = parse("")) {
|
||
std::cout << "value: " << *num << '\n';
|
||
} else {
|
||
num.error().print();
|
||
}
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
```bash
|
||
Sorry your function doesn't work!
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
<!-- .slide: style="font-size: 0.68em" -->
|
||
___
|
||
|
||
## Exercise
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Create an exception class `Exception` which inherits from `std::exception` and two classes `ParserException` and `ReaderException` which inherit form class `Exception`.
|
||
* <!-- .element: class="fragment fade-in" --> Rewrite a code to `ParserException` in the method `parse` and `ReaderException` in the method `read`.
|
||
* <!-- .element: class="fragment fade-in" --> Try to catch errors in `main` and print them
|
||
* <!-- .element: class="fragment fade-in" --> Rewrite a code once again and return `nullopt` when an error occurs instead of throwing an exception |