Exception
An exception is an unscheduled event that disrupts program execution; used to detect undefined instructions.
Example from my Biquadris project (shortened for conciseness)
BlockCommand::BlockCommand(const std::string& name): Command{name} {
if (name.length() != 1) {
throw std::invalid_argument{"invalid block type"};
}Also see Exception Safety.
CS247
I didn’t really think about exceptions before, but if you want to be a 10x Developer, you need to recognize that exceptions significantly change control flow! We no longer have the guarantee of sequential execution.
You need to think very hard about Exception Safety.
Motivation
To first motivate this, you need to understand Error Handling. C++ enables error handling through the use of Exceptions.
V.at(100) fetches V[100] if the value exists, otherwise, throws an exception.
try {
cout << v.at(100) << endl;
} catch(std::out_of_range r) {
cout << "Range error" << r.what() << endl;
}ris just an object, class type isstd::our_of_range(included in<stdexcept>)- The
.what()method returns a string describing the exception.
Force the programmer to deal with the error because the control flow jumps.
Vector knows the error happened but not how to fix it. We know how to fix it, but not how the error occurred.
To raise an exception ourself, we use the throw keyword. We can throw any value, but keep in mind that <stdexcept> has objects for common scenarios like out_of_range, logic_error, invalid_argument.
Stack Unwinding
When an exception is raised, control flow steps. We search through the stack upwards looking for a handler for this type of exception. This is called Stack Unwinding. Destructors are run for objects stored on the stack during the process of stack unwinding.
If a handler is found, we jump to that point. If no handler is found, program crashes.
Example of stack unwinding:
void f() {
throw std::out_of_range{"f threw"};
}
void q() { f();};
void h() {q();};
int main() {
try { h();}
catch (std::out_of_range r) {
cout << r.what();
}
}- Main calls
h,hcallsq,qcallsf, throws, stack unwinding throughq,h, jump tocatchblock in main.
Handling Multiple Errors
Multiple errors may be handled via multiple catch blocks.
try {...}
catch (out_of_range r) {...}
catch (logic_error e) {...}
catch (invalid_argument u) {...}
catch(...) {...} // yes, catch(...) is the catch-all syntax which catches any type of exception ... Literally 3 dotsRethrowing Errors
One handler can also deal with part of an error, rethrow the exception to allow someone else to deal with it (Stack Unwinding occurs).
void calculation(DataStructure& ds) {
...
throw (ds_error) {...};
}
void DataStructureHandler(DataStructure& ds) {
try {calculation(ds);}
catch (ds_error e) {
// fix the data structure issue
throw prompt_input_error{...};
}
}
int main() {
DataStructure ds;
string s;
while (cin >> s) {
try {
DataStructureHandler(ds);
} catch (prompt_input_error e) {
cout << "invalid input";
}
}
}We can also rethrow the same exception for someone else to deal with.
try {...}
catch (std::exception& e) {
...
throw; // IMPORTANT, DO NOT DO "throw e";
}Why here do I just say
throwrather thanthrow e?This wasn’t just an accident.
throw eperforms a copy in order to throw this exception- Remember that copy constructors (any type of constructor) cannot be virtual. Therefore, the static type is used for the copy
If you throw a range_error and catch via std::exception& catch block,
throwrethrowsrange_errorthrow ecatch astd::exceptioncopied from therange_error, we lose the dynamic type.
Generally, catch blocks should catch by reference to avoid avoid copies.
catch(...) vs catch(std::exception& e)?
The professor introduced both syntaxes, I thought they were exactly the same thing. Turns out, there is a difference:
try {...}
catch(const std::exception& e){} // Will catch `std::exceptions` only.
catch(...) {throw; }; // Will catch everything there after.With exceptions, you can handle integers and other types (http://www.cplusplus.com/doc/tutorial/exceptions/). I guess, for example, you can throw 5.
For example:
catch(int e)Destructor Exceptions
Never let a destructor throw an Exception!
Default behavior: Program immediately crashes.
We can allow exceptions thrown from destructors by tagging them noexcept (false), see noexcept.
For example,
class A {
public:
...
~A() noexcept (false) {...}
}Be careful with
noexcept(false)DestructorsAs we know, when we throw an exception, Stack Unwinding occurs. During this process destructors are running for stack allocated objects. If one of these destructors throws, now we have 2 active exceptions!
- 2 active exceptions = program crash (this behavior cannot be changed)
Standard
<stdexcept>errorsSome standard
<stdexcept>errors include:
out_of_rangelogic_errorinvalid_argumentstd::bad_alloc occurs when the new operator fails to allocate the requested space, more here