This answer might be above your level, but I hope it at least gives you a basic understanding of what happens when an exception is thrown. And it will give you some googleable terms at the very least.
A throw
statement is not an immediate function call. It is a point in a program where a certain object is constructed (be it an int
, std::exception
, std::runtime_error
, or any other type of object).
After the exception object is constucted, the stack is unwound. This is an important concept and I will try to sketch it in a few words. Unwinding the stack means all the functions you have called are traversed in reverse order, and all objects allocated (on the stack) in those functions are destructed. This process continues backwards until a catch
block is reached that catches your type of exception (normal function overload resolution rules apply here, so conversions are possible).
An illustrative example:
#include <iostream>
#include <memory>
#include <string>
class my_exception
{
public:
my_exception(const std::string& message) : message(message) {}
const std::string& what() { return message; }
private:
const std::string message;
};
void boo()
{
int local = 5; // local, "automatic storage duration" variable
throw my_exception("boo threw");
}
void bam()
{
int* i = new int(42); // dynamically allocated int, unowned, accessible
std::unique_ptr<int> j(new int(43)); // dynamically allocated int, owned by a smart pointer with automatic storage duration
boo();
delete i;
}
void f()
{
try
{
bam();
}
catch(const my_exception& e)
{
std::cout << e.what();
}
}
int main()
{
f();
}
The process that perspires is the following:
main
is entered
f()
is called
try
block is entered
bam()
is called
- an
int
is dynamically allocated. A pointer to int
named i
with automatic storage duration is constructed (on the stack)
- a
unique_ptr
object containing a pointer to another dynamically allocated int
is created.
boo()
is called
- An
int
with automatic storage duration in constructed.
- An object of type
my_exception
is constructed and stack unwinding begins.
boo()
is left, and local
is destructed (cleaned up)
- we're back in
bam()
and also leaving it behind: first j
's destructor is called, which calls delete
on the integer with value 43
. Then the pointer object i
is destructed, but the integer it pointed to is not delete
d. We have a memory leak (the delete i;
statement is never reached).
- we're back in
f()
, where a friendly catch
catches our my_exception
and outputs the message
though std::cout
.
To answer your questions:
1) No, a more complex process happens, which eventually ends in step 12 which might resemble a function call, but it is not really.
2) See above.
3) A catch
"receives" an object. It works much like function overloads. The best one is picked. If there is no match, unwinding continues. e
doesn't "receive" anything. It might be constructed from whatever is thrown, but that requires that the type of e
has the right conversion/constructor.
4) No. That specifies inheritance. Example:
#include <stdexcept>
class my_exception : public std::exception
{
public:
my_exception(const std::string& message) : std::exception(message) {}
}
This my_exception
class inherits the what()
function defined in std::exception
, so I don't need to define it myself.