Controlo de Excepções class X { public: X(const X&) { X() { virtual ~X() { cout << "copy constructor\n"; cout << "constructor\n"; } cout << ”destructor\n"; } }; void f(X&){} void ff(void) { X x; cout << "f (call)" << endl; f(x); cout << "f (return)" << endl; } constructor f (call) f (return) destructor } void ff(void) { X x; cout << "before throw" << endl; throw x; cout << "after throw" << endl; } void fff(void) { try { ff(); } catch(...) { cout << "catch\n"; } } int main(int argc, char* argv[]) { fff(); return 0; } constructor before throw copy constructor destructor catch destructor void ff(void) { X x; cout << "before throw" << endl; x; throw &x; cout << "after throw" << endl; } void fff(void) { try { ff(); } catch(...) { cout << "catch\n"; } } int main(int argc, char* argv[]) { fff(); return 0; } constructor before throw destructor catch void ff(void) { X x; cout << "before throw" << endl; throw x; cout << "after throw" << endl; } void fff(void) { try { ff(); } catch(X x) { cout << "catch\n"; } catch(...) } int main(int argc, char* argv[]) { fff(); return 0; } constructor before throw copy constructor copy constructor destructor catch destructor destructor void ff(void) { static X X x; x; cout << "before throw" << endl; x; throw &x; cout << "after throw" << endl; } void fff(void) { try { ff(); } catch(X* x) { cout << "catch\n"; } catch(...) } int main(int argc, char* argv[]) { fff(); return 0; } constructor before throw destructor catch void ff(void) { X x; cout << "before throw" << endl; throw x; cout << "after throw" << endl; } void fff(void) { try { ff(); } catch(...) catch(X& x) { cout << "catch\n"; } static X x; constructor before throw copy constructor destructor catch destructor } int main(int argc, char* argv[]) { fff(); return 0; } slicing problem class X{ ... }; void f() { static X x; // global ................ throw &x; //ponteiro } void ff() { try { f(); } catch(X *x) { ... } // ponteiro } class X { public: vitrual void f() throw(); }; class Y : public X { ... }; class Z : public Y { public: vitrual void f() throw(); ..................... }; void ff() { ............... if( ... ) throw Z(); } slicing problem void fff() { try { ff(); } catch (X x) { cerr << x.f(); } } // chama X::f(), nunca Z::f() slicing problem static X x; void ff() { .............. if( ... ) throw Z(); } void fff() { try { ff(); } catch (X& x) { cerr << x.f(); } } // agora chama Z::f() void f(int i) { try { if( . . . ) } } catch(float f) { ........ } } throw i; extern void f(); void ff() throw (int); void ff() throw(); void f() throw(X,Y) { ... } - específica a lista (X,Y) das excepções que podem ser geradas pela função f. Se a função f gerar outras excepções (que não são X nem Y), a função especial que se chama unexpected vai ser chamada automaticamente. Por defeito a função unexpected chama a função terminate. Podemos redefinir estas funções usando funções da biblioteca tais como set_terminate e set_unexpected. A função terminate é chamada nas seguintes ocasiões: 1. É impossível encontrar o respectivo bloco catch. 2. Existem erros na pilha. 3. Existem erros no destrutor que destroí os objectos automáticos (locais). Sumário de excepções 1. O mecanismo de controlo de excepções permite interromper o programa em pontos onde aparece uma situação anormal. Mais frequentemente este mecanismo é usado para procurar os erros. 2. Quando o programa é interrompido o controlo, e possivelmente os dados ,vão ser passados do ponto de execução ao ponto de um programa predefinido onde a excepção vai ser tratada. 3. Este mecanismo pode ser usado só para as excepções sincronizadas. Isto significa que estas excepções só podem ser geradas dentro do programa. Por exemplo, não é possível considerar excepções que vão ser geradas ao pressionar as teclas Ctrl-C. 4. Para o controlo das excepções a linguagem C++ introduz três novas palavras chaves que são throw, try e catch. 5. As excepções podem ser de qualquer tipo mas mais frequentemente têm o tipo class ou struct. 6. O bloco do código que pode ter excepções deve estar delimitado pela palavra chave try e parêntesis ( {} ). Depois do bloco try deve seguir pelo menos um bloco catch que se chama o manipulador da excepção. Este bloco também deve estar em parêntesis ( {} ). É permitido usar vários blocos catch após do bloco try. 7. A excepção é gerada quando a instrução throw é activada. Neste caso as seguintes acções vão ser executadas: 7.1. Procuramos o respectivo manipulador, i.e. o primeiro bloco catch que tem o argumento associado com a instrução throw. 7.2. Se o manipulador for encontrado começamos o processo que se chama “stack unwinding”. Isto significa que todos os destrutores para os objectos locais construídos a partir do ínicio do bloco catch vão ser invocados. De notar que os destrutores vão ser chamados só para os objectos que foram construídos completamente. 7.3. O controlo vai ser passado ao manipulador que foi encontrado. 8. A instrução throw passa o controlo ao primeiro bloco catch, i.e. ao manipulador cujo bloco try está a ser executado. 9. A palavra chave throw pode ser usada das seguintes formas: 9.1. throw operand; - activa o indicador (operador) da excepção, que vai ser passado ao respectivo manipulador. 9.2. throw ; - activa novamente a mesma excepção. 9.3. void f() throw(X,Y) { ... } - específica a lista (X,Y) das excepções que podem ser geradas pela função f. Se a função f gerar outras excepções (que não são X nem Y), a função especial que se chama unexpected vai ser chamada automaticamente. 10. A lista das excepções não faz parte da função. Para esta lista existem os seguintes convénios: 10.1. A função sem a lista pode gerar qualquer excepção. 10.2. A função com a lista vazia tal como throw() não pode gerar qualquer excepção. 10.3. A função que pode gerar a excepção do tipo (da classe) X pode também gerar qualquer excepção da classe Y (XY) se a classe base X tiver o atributo público, por exemplo class Y : public X {...}; 11. O manipulador das excepções pode ser apresentado das duas seguintes formas: try { .......................... } A: catch(Type Object) { ............ } B: catch(...) { ............ } O tipo do argumento Object pode ser: Type, Type&, const Type, const Type&. O argumento é aceite se pelo menos uma das seguintes regras for válida: a) o Objecto tem o tipo Type. b) Type é um tipo da classe base para o Objecto (que pode ser aceite). c) Type é um ponteiro e o Objecto tem o tipo ponteiro que pode ser convertido ao Type através de uso das regras da linguagem. 12. O manipulador catch(...) trata qualquer excepção independente do seu tipo. 13. A procura nos blocos catch é feita pela sequência em que estes blocos se encontram no programa. 14. Vamos assumir que temos a classe Base e a classe Derivada (Base Derivada). Neste caso, se a classe Base for encontrada antes da classe Derivada vamos ter um erro porque a classe Derivada nunca vai ser executada. 15. O manipulador catch(...) deve ser sempre o último bloco na lista dos blocos catch. 16. Depois de terminar o bloco catch o controlo vai ser passado ao fim da lista que contém todos os blocos catch e depois para o bloco try. 17. Para sair dos blocos catch ou try não é permitido usar a instrução goto. 18. A função terminate é chamada nas seguintes ocasiões: 18.1. É impossível encontrar o respectivo bloco catch. 18.2. Existem erros na pilha. 18.3. Existem erros no destrutor que destroí os objectos automáticos (locais). 19. Por defeito a função unexpected chama a função terminate. Podemos redefinir estas funções usando funções da biblioteca tais como set_terminate e set_unexpected.