•
Tratamento de Erros em C++
Disparando novamente
uma exceção
Processando falhas de New
A classe auto_ptr e a alocação dinâmica de memória
Hierarquia de exceções da biblioteca
padrão
• Tratamento
de Erros em C++
O tratamento de exceções em C++ se destina a situações em que
a função que descobre um errro está impossibilitada de tratá-lo. Tal função disparará
( throw ) uma exceção.
Colocamos dentro do bloco try o que poderá gerar erro. O bloco
try é seguido pelo bloco catch ( um ou mais ) que especificam o tipo de exceção
que ele pode capturar e tratar.
Se nenhum tratador for encontrado, é chamada por default terminate e chama a função abort.
Exemplo: Divisão por zero
#include <iostream>
class Error {
public:
char * msg;
Error() { msg = "Error dividir por 0"; }
};
int main()
{
int a = 10, b = 0;
double result;
std::cout << " 10 divide por 0 " << std::endl;
try
{
if ( b == 0 )
{
throw Error();
}
result = a / b;
}
catch ( Error x )
{
std::cout << x.msg << std::endl;
}
system("pause");
return 0;
}
A palavra chave throw é usada para indicar que uma exceção aconteceu.
Isto é chamado de disparar uma exceção. Um throw normalmente especifica um operando.
O Operando de um throw pode ser de qualquer tipo. Se o operando é um objeto,
chama-lo de objeto de exceção.
Se for necessário passar informações sobre o erro que causou
uma exceção, tal informação pode ser colocada no objeto de disparo. O tratador
catch então vai conter um nome de parâmetro através do qual as informações podem
ser referenciadas.
Uma exceção executada fora do bloco try provoca uma chamada
a terminate.
Um catch pode ter apenas um único parâmetro, especificar mais
de um é um erro de sintaxe.
Colocar um catch seguido por parenteses incluindo reticências
catch ( ... )
significa capturar todas as exeções. LOGICAMENTE, ELES
devem ser o ultimo da lista!
#include <iostream>
int main()
{
int i = 5, a = 0;
double b;
try
{
b = i / a;
}
catch ( ... )
{
std::cout << "Impossivel" << std::endl;
}
system("pause");
return 0;
}
É possivel que nenhum tratador corresponda
a um objeto disparado em particular. Isso faz com que a procura por uma correspondência continue
no próximo bloco try mais externo. Caso nenhum disparador seje encontrado,
abort é executado.
Colocar um catch que captura um objeto de uma
classe base antes de um catch que captra um objeto de uma classe derivada é um erro de lógica.
O catch da classe derivada nunca seria executado.
Colocar um catch ( void * ) capturaria as exceções de tipos
ponteiro, de modo que os outros tratadores nunca seriam executados. Somente catch
( ... ) deve vir após catch ( void * )
Para disparar objetos consts o tipo catch também
deve ser const.
O bloco try tem seu próprio escopo.. objetos criados nele não
são acessíveis para o bloco catch abaixo.
Se existe um erro no bloco catch, o tratador chamado para esse erro será o
proximo bloco catch do proximo bloco try mais externo.
Disparando novamente uma exceção
É possível que o tratador que captura uma exceção
decida que ele não pode processar a exceção ou que ele simplesmente
queira liberar recursos antes de deixar algum outro trata-la. Nesta caso, o tratador
pode simplesmente disparar novamente a exceção, com o comando throw.
Uma especificação de exceção enumera uma lista de exceções que podem ser disparadas
por uma função.
int func( double i ) throw( a, b, c ) { //... }
Isto é chamado de lista de throw ou especificação de exceção. Ela lista as
exceções que podem ser disparadas.
Em funções, throw vazio chama unexpected e não executa nenhuma exceção, isso
também acontece quando tenta-se executar uma exceção que não foi especificada
na lista.
Funções em lista de throw podem executar qualquer exceção.
O significado de unexpected por ser definido em set_unexpected.
A função set_terminate pode especificar a função que será chamada quando terminate
é chamada. Caso contrário, terminate chama abort.
Os prototipos para essas funções estão em <exception>
#include <iostream>
#include <stdexcept>
using std::runtime_error;
void f3() throw( runtime_error )
{
throw runtime_error( "runtime_erro na funcaoo 3");
}
void f2() throw( runtime_error )
{
f3();
}
void f1() throw( runtime_error )
{
f2();
}
int main()
{
try
{
f1();
}
catch( runtime_error e )
{
std::cout << "Ocorreu excecao: " << e.what() << std::endl;
}
return 0;
}
Acompanhe o raciocionio:
Em main, o bloco try chama f1(). Em seguida f1() chama f2(). f2() chama f3().
f3() dispara um objeto exception. Como em f3 não existe try, acontece o desempilhamento
da pilha - f3() termina, como f2 não existe try, f2 termina, como f1 também não
tem try, f1 termina. Como o bloco try está em main, a exceção pode ser capturada
e processada no primeiro tratador catch correspondente depois do bloco try.
Processando falhas de New
Existem vários metodos de se lidar com falhas de New. Até aqui usamos a macro
assert para tratar um new com erro, ele retornava 0 ou 1. Mais esta não é a melhor
forma de se fazer isso. O assert não permite recuperar falhas de new.
O padrão C++ diz que quando NEW falha, é disparado uma excessão bad_alloc ( definida em <new> ).
Uma forma de se recuperar falhas de new é testando o proprio ponteiro criado,
da seguinte forma:
double *ponteiro[50];
ponteiro[1] = new double[5000000]; // isso pode funcionar
if ( ponteiro[ 1 ] == 0 ) // new falhou
Agora, vou demonstrar o mesmo exemplo com bad_alloc.
#include <iostream>
#include <new>
int main()
{
double *ponteiro[50];
try
{
for ( int i = 0; i<50; i++ )
{
ponteiro[i] = new double[5000000];
std::cout << "Alocados 5000000 doubles em ponteiro[ " << i << " ]
\n";
}
}
catch( std::bad_alloc exception )
{
std::cout << " ERRO>> " << exception.what() << std::endl;
}
return 0;
}
Cuidado com este programa!! ehheehhe
Bom, o que acontece é que alguns compiladores tem suporte a tratamento de new
com 0 quando falha, e alguns, quando incluido o arquivo de cabeçalho new executam
bad_alloc por default.
Bom... alguns compiladores executam por default bad_alloc sem incluir o cabeçalho
new. Se vc não quer isso, deve usar o termo nothrow(
do tipo nothrow_t ), que
é usado como:
double *ponteiro = new ( nothrow ) double[ 500000 ];
O comando indica que a versão de new
não dispara exceções bad_alloc.
Existe um recurso adicional que pode ser usado para tratamento
de falhas em new. A função set_new_handler ( do cabeçalho new ) aceita como seu
parametro um ponteiro de função para uma função que não recebe nenhum argumento
e retorna void. O ponteiro de função é registrado como a função a ser executada
quando new falha. Esse tratamento executa sempre, independente de onde o erro
estiver. Uma vez registrado set_new_handler, bad_alloc
não será executado.
O Padrão C++ especifica que a função tratadora de new deveria executar uma
das tarefas seguinte:
1 - Tornar disponível mais memória, apagando outra memória dinamicamente alocada,
e retornar ao laço no operador new para tentar alocar a memória novamente.
2 - Disparar uma exceção do tipo bad_alloc.
3 - Chamar a função abort ou exit ( ambas do arquivo de cabeçalho <csdtlib
> ) para terminar o programa.
#include <iostream>
#include <new>
#include <cstdlib>
using std::cerr;
using std::set_new_handler;
void customNewHandler()
{
cerr << "Custom foi chamada";
abort();
}
int main()
{
double *ponteiro[50];
set_new_handler( customNewHandler );
prt[1] = new double[ 500000 ]; // caso erro, executa new handler
return 0;
}
A classe auto_ptr e a alocação dinâmica de memória
O que acontece quando você aloca um objeto na memória e acontece uma exceção
antes de você acionar o delete para aquele ponteiro??? Ela vai ficar perdida.
O padrão C++ fornece o gabarito de classe auto_ptr no arquivo de cabeçalho <memory> para lidar com esta situação.
Um objeto da classe auto_ptr mantem um ponteiro para memória
dinamicamente alocada. Quando um objeto sair do escopo, ele executa a operação
delete sobre seu membro de dados ponteiro. O gabarito auto_ptr fornece
operadores * e -> de modo que um objeto auto_ptr possa ser usado como uma variável
ponteiro regular.
Esta técnica pode prevenir perdas de memória.
#include <iostream>
#include <memory>
class Integer {
public:
Integer( int i=0 ) { this->numero = i; std::cout << "Integer Iniciado" << std::endl;
}
~Integer() { std::cout << "Integer DESTRUIDO" << std::endl;
}
int get() { return this->numero; }
void set( int i ) { this->numero = i; }
private:
int numero;
};
int main()
{
std::auto_ptr< Integer > aInteger ( new Integer(10) );
std::cout << "Valor: " << aInteger->get() << std::endl;
aInteger->set( 99 );
std::cout << "Valor: " << (*aInteger).get() << std::endl;
return 0;
}
Hierarquia de exceções da biblioteca padrão
C++ impoões uma hierarquia para as classes de exceções. Esse hierarquia é
encabeçada pela classe base exception ( <exception>
) que contém a função what()
que é sobrescrita em cada classe derivada, para emitir uma mensagem de erro apropriada.
As derivadas imediatas de exception são runtime_error e logic_error ( <stdexcept>
).
Também de exception, bad_alloc disparada de new,
bad_cast que é disparada por dynamic_cast e bad_typeid que é disparada por typeid ( vamos
ver estes termos todos ). Incluindo std::bad_exception em throw.
A classe logic_error é a classe base de várias classes de exceção padrão que
indicam erros de lógica em programas que podem frequentemente ser evitados.
A classe invalid_argument indica que um parâmetro inválido
foi passado para uma função.
A classe length_error indica que um comprimento maior que o tamanho máximo
permitido para o objeto que está sendo tratado foi usado para aquele objeto.
A classe out_of_range indica que um valor tal como um subscrito para um array
caíram fora do intervalo válido.
A classe runtime_error é a classe base de várias outras classes de exceção
padrão que indicam erros em um programa que só podem ser descobertos durante
a execução. A classe overflow_error indica que ocorreu um erro de overflow em
uma operação aritmética. A classe underflow_error indica que aconteceu um erro
de underflow em uma operação aritmética.
|