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 deleted. 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.