Exceções e desenrolamento da pilha em C++
No mecanismo de exceção do C++, o controle move-se da instrução throw para a primeira instrução catch que pode manipular o tipo lançado. Quando a instrução catch é acessada, todas as variáveis automáticas que estão no escopo entre o as instruções throw e catch são destruídas em um processo que é conhecido como desenrolamento de pilha. No desenrolamento de pilha, a execução ocorre da seguinte maneiro:
O controle acessa a instrução
try
em uma execução sequencial normal. A seção protegida no blocotry
é executada.Se nenhuma exceção for gerada durante a execução da seção protegida, as cláusulas
catch
que seguem o blocotry
não serão executadas. A execução continua na instrução depois da última cláusulacatch
que segue o blocotry
associado.Se uma exceção for lançada durante a execução da seção protegida ou em qualquer rotina que a seção protegida chamar direta ou indiretamente, um objeto de exceção será criado a partir do objeto criado pelo operando
throw
. (Isso implica que um construtor de cópia pode ser envolvido.) Nesse ponto, o compilador procurará por uma cláusulacatch
em um contexto de execução mais alto que possa tratar uma exceção do tipo que foi gerada, ou por um manipuladorcatch
que possa tratar qualquer tipo de exceção. Os manipuladorescatch
são verificados na ordem em que aparecem após o blocotry
. Se nenhum manipulador apropriado for localizado, o próximo blocotry
dinamicamente delimitado será verificado. Esse processo continua até que o blocotry
delimitado mais externo seja verificado.Se mesmo assim um manipulador correspondente não for localizado, ou se uma exceção ocorrer durante o processo de desenrolamento antes que o manipulador obtenha o controle, a função de tempo de execução predefinida
terminate
é chamada. Se uma exceção ocorrer depois que a exceção for lançada, mas antes do início do desenrolamento,terminate
é chamada.Se um manipulador
catch
correspondente for localizado, e fizer a captura por valor, o parâmetro formal será inicializado copiando o objeto de exceção. Se a captura for feita por referência, o parâmetro é inicializado para consultar o objeto de exceção. Depois que o parâmetro formal for inicializado, o processo de desenrolamento de pilha é iniciado. Isso envolve a destruição de todos os objetos automáticos que foram completamente construídos, mas ainda não destruídos, entre o início do blocotry
associado ao manipuladorcatch
e o site que gerou a exceção. A destruição ocorre na ordem inversa da construção. O manipuladorcatch
é executado e o programa retoma a execução após o último manipulador, ou seja, na primeira instrução ou constructo que não seja um manipuladorcatch
. O controle só pode inserir um manipuladorcatch
com uma exceção gerada, nunca por meio de uma instruçãogoto
ou de um rótulocase
em uma instruçãoswitch
.
Exemplo de desenrolamento de pilha
O exemplo a seguir demonstra como a pilha é desenrolada depois que uma exceção é lançada. A execução do thread ignora a instrução throw em C
e passa à instrução catch em main
, desenrolando cada função ao longo do caminho. Observe a ordem na qual os objetos Dummy
são criados e destruídos à medida que saem do escopo. Observe também que nenhuma função é concluída, exceto main
, que contém a instrução catch. A função A
nunca retorna da sua chamada a B()
, e B
nunca retorna de sua chamada a C()
. Se você remover o comentário da definição do ponteiro Dummy
e a instrução delete correspondente, e executar o programa em seguida, observará que o ponteiro nunca será excluído. Isso mostra o que pode acontecer quando as funções não fornecem uma garantia de exceção. Para obter mais informações, consulte Como: Design para exceções. Se você fizer comentários fora da instrução catch, observe o que acontece quando um programa é encerrado devido a uma exceção sem tratamento.
#include <string>
#include <iostream>
using namespace std;
class MyException{};
class Dummy
{
public:
Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); }
Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); }
~Dummy(){ PrintMsg("Destroyed Dummy:"); }
void PrintMsg(string s) { cout << s << MyName << endl; }
string MyName;
int level;
};
void C(Dummy d, int i)
{
cout << "Entering FunctionC" << endl;
d.MyName = " C";
throw MyException();
cout << "Exiting FunctionC" << endl;
}
void B(Dummy d, int i)
{
cout << "Entering FunctionB" << endl;
d.MyName = "B";
C(d, i + 1);
cout << "Exiting FunctionB" << endl;
}
void A(Dummy d, int i)
{
cout << "Entering FunctionA" << endl;
d.MyName = " A" ;
// Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
B(d, i + 1);
// delete pd;
cout << "Exiting FunctionA" << endl;
}
int main()
{
cout << "Entering main" << endl;
try
{
Dummy d(" M");
A(d,1);
}
catch (MyException& e)
{
cout << "Caught an exception of type: " << typeid(e).name() << endl;
}
cout << "Exiting main." << endl;
char c;
cin >> c;
}
/* Output:
Entering main
Created Dummy: M
Copy created Dummy: M
Entering FunctionA
Copy created Dummy: A
Entering FunctionB
Copy created Dummy: B
Entering FunctionC
Destroyed Dummy: C
Destroyed Dummy: B
Destroyed Dummy: A
Destroyed Dummy: M
Caught an exception of type: class MyException
Exiting main.
*/
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de