Когда вы выбрасываете ex, он копируется в специальную область памяти, используемую для брошенных объектов исключения. Такое копирование выполняется обычным конструктором копирования.
Вы можете легко увидеть это из этого примера:
#include <iostream>
void ThrowIt();
class TestException
{
public:
TestException()
{
std::cerr<<this<<" - inside default constructor"<<std::endl;
}
TestException(const TestException & Right)
{
(void)Right;
std::cerr<<this<<" - inside copy constructor"<<std::endl;
}
~TestException()
{
std::cerr<<this<<" - inside destructor"<<std::endl;
}
};
int main()
{
try
{
ThrowIt();
}
catch(TestException & ex)
{
std::cout<<"Caught exception ("<<&ex<<")"<<std::endl;
}
return 0;
}
void ThrowIt()
{
TestException ex;
throw ex;
}
Пример вывода:
matteo@teolapubuntu:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic ExceptionStack.cpp -o ExceptionStack.x
matteo@teolapubuntu:~/cpp/test$ ./ExceptionStack.x
0xbf8e202f - inside default constructor
0x9ec0068 - inside copy constructor
0xbf8e202f - inside destructor
Caught exception (0x9ec0068)
0x9ec0068 - inside destructor
Кстати, вы можете видеть здесь, что область памяти, используемая для брошенного объекта (0x09ec0068), определенно далека от той, что была у исходного объекта (0xbf8e202f): стек, как обычно, имеет высокие адреса, а память, используемая для брошенного объекта, находится в виртуальном адресном пространстве. Тем не менее, это деталь реализации, поскольку, как указывалось в других ответах, стандарт ничего не говорит о том, где должна быть память для брошенного объекта и как она должна быть распределена.