C++
Home

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.