Erros e tratamento de exceções (C++ moderno)
No C++ moderno, na maioria das situações, a maneira preferencial de relatar e manipular erros lógicos e erros de tempo de execução é usar exceções. Isso será especialmente verdadeiro quando a pilha talvez contenha várias chamadas de função entre a função que detecta o erro e a função com o contexto para saber como manipulá-lo. As exceções proporcionam uma maneira formal e bem definida para o código que detectar erros para transmitir as informações à pilha de chamadas.
Erros de programa estão geralmente divididos em duas categorias: erros lógicos causados por erros de programação, por exemplo, um erro de "índice um erro fora do intervalo" e erros de tempo de execução que estão além do alcance do programador, por exemplo, um erro de "serviço de rede indisponível". Na programação no estilo C e em COM, o relatório de erros é gerenciado retornando um valor que representa um código de erro ou um código de status para uma função específica, ou definindo uma variável global que o chamador pode opcionalmente recuperar após cada chamada de função para ver se os erros foram relatados. Por exemplo, a programação COM usa o valor de retorno HRESULT para comunicar erros ao chamador, e a API do Win32 tem a função GetLastError para recuperar o erro mais recente que foi reportado pela pilha de chamadas. Em ambos os casos, cabe ao chamador reconhecer o código e responder a ele de forma apropriada. Se o chamador não tratar explicitamente do código de erro, o programa poderá falhar sem aviso ou continuar a executar com dados incorretos e produzir resultados incorretos.
As exceções são preferenciais no C++ moderno pelos seguintes motivos:
Uma exceção força o código de chamada para reconhecer uma condição de erro e para tratá-lo. Execução do programa de parada de exceções sem tratamento.
Uma exceção pula para o ponto na pilha de chamadas que pode manipular o erro. As funções intermediárias podem deixar a exceção propagar. Não precisam estar coordenados com outras camadas.
O mecanismo de desenrolamento de pilha de exceção destrói todos os objetos no escopo de acordo com as regras bem definidas após o lançamento de uma exceção.
Uma exceção permite uma separação limpa entre o código que detecta o erro e o código que trata o erro.
O exemplo a seguir mostra como a sintaxe necessária para lançar e capturar exceções em C++.
#include <stdexcept>
#include <limits>
#include <iostream>
using namespace std;
class MyClass
{
public:
void MyFunc(char c)
{
if(c < numeric_limits<char>::max())
throw invalid_argument("MyFunc argument too large.");
//...
}
};
int main()
{
try
{
MyFunc(256); //cause an exception to throw
}
catch(invalid_argument& e)
{
cerr << e.what() << endl;
return -1;
}
//...
return 0;
}
Exceções no C++ são semelhantes àquelas em linguagens como C# e Java. No bloco try, se uma exceção é gerada , ela será capturada pelo primeiro bloco associado catch cujo tipo corresponde ao de exceção. Ou seja, a execução vai da instrução throw à instrução catch. Se nenhum bloco catch for encontrado, std::terminate será chamado e o programa será encerrado. Em C++, qualquer tipo pode ser lançado; no entanto, recomendamos lançar um tipo derivado direta ou indiretamente de std::exception. No exemplo anterior, o tipo de exceção, invalid_argument, é definido na biblioteca padrão no arquivo de cabeçalho <stdexcept>. O C++ não fornece, e não requer, um bloco finally para verificar se todos os recursos são liberados se uma exceção é lançada. A aquisição de recurso é o idioma de inicialização (RAII), que usa ponteiros inteligentes, oferece a funcionalidade necessária para a limpeza de recurso. Para obter mais informações, consulte Como projetar tendo em vista a segurança da exceção. Para obter informações sobre o mecanismo de pilha de desenrolamento do C++, consulte Exceções e desenrolamento da pilha em C++.
Diretrizes básicas
A manipulação de erro robusta é desafiadora em qualquer linguagem de programação. Embora as exceções forneçam vários recursos que suportam a manipulação de erros adequada, não podem fazer todo o trabalho para você. Para alcançar os benefícios do mecanismo de exceção, tenha as exceções em mente ao criar seu código.
Use as verificações para verificar se há erros que nunca deveriam ocorrer. Use as exceções para verificar se há erros que podem ocorrer, por exemplo, erros na validação de entrada em parâmetros de funções públicas. Para obter mais informações, consulte a seção Exceções vs. Asserções.
Use as exceções quando o código que trata o erro pode ser separado de código que detecta o erro por uma ou mais chamadas de função interveniente. Considere se usar códigos de erro em vez de loops críticos de desempenho quando o código que trata o erro estiver estritamente acoplado ao código que o detecta. Para obter mais informações sobre quando não usar exceções, consulte When Not to Use Exceptions.
Para cada função que pode lançar ou propagar uma exceção, forneça uma das três garantias de exceção: a garantia forte, a garantia básica ou a garantia de nothrow (noexcept). Para obter mais informações, consulte Como projetar tendo em vista a segurança da exceção.
Lance exceções por valor, capture-as por referência. Não capture o que você não pode manipular. Para obter mais informações, consulte Diretrizes para lançar e capturar exceções (C++).
Não use as especificações de exceção, que são substituídas no C++11. Para obter mais informações, consulte a seção Especificações de exceção e noexcept.
Use os tipos de exceção padrão de biblioteca quando forem necessários. Gere tipos de exceção personalizados da hierarquia de classe de exceção. Para obter mais informações, consulte Como: usar os objetos de exceção de biblioteca padrão.
Não permita que as exceções escapem dos destruidores ou das funções de desalocação de memória.
Exceções e desempenho
O mecanismo de exceção terá um custo de desempenho mínimo caso nenhuma exceção seja lançada. Se uma exceção é lançada, o custo de passagem e de desenrolamento de pilha são aproximadamente comparáveis aos custos de uma chamada de função. Estruturas de dados adicionais são necessárias para controlar a pilha de chamadas após um bloco de try ser inserido, e instruções adicionais são necessárias para desenrolar a pilha se uma exceção for lançada. No entanto, na maioria dos cenários, o custo no desempenho e nos requisitos de memória não são significativos. O efeito adverso de exceções sobre o desempenho provavelmente será significativo somente em sistemas de memória muito restrita ou em loops de desempenho críticos. onde é provável que um erro ocorra regularmente e o código para manipulá-lo está acoplado ao código que o reporta. Em qualquer caso, é impossível saber o custo real de exceções sem criar perfil e medir. Mesmo nesses casos raros em que o custo é significativo, você pode pesá-lo em relação ao aumento da exatidão, a capacidade de manutenção mais fácil e outras vantagens que são fornecidas por uma política de exceções bem estruturada.
Exceções x afirmações
Exceções e afirmações são dois mecanismos diferentes para detectar erros em tempo de execução em um programa. Use as verificações para testar as condições durante o desenvolvimento, as quis nunca devem ser verdadeiras se todo o seu código estiver correto. Não faz sentido manipular esse erro usando uma exceção porque o erro indica que algo no código precisa ser corrigido e não representa uma condição da qual o programa deverá se recuperar em tempo de execução. Uma assert para a execução na instrução de modo que você pode inspecionar o estado de programa no depurador; uma exceção retoma a execução do primeiro manipulador catch adequado. Use as exceções para verificar as condições de erro que podem ocorrer em tempo de execução mesmo se o seu código estiver correto, por exemplo, “arquivo não encontrado” ou “sem memória.” Você pode desejar recuperar essas condições, mesmo se a recuperação apenas enviar uma mensagem para um log e encerrar o programa. Sempre verifique argumentos para funções públicas usando exceções. Mesmo que sua função não contenha erros, é possível que você não tenha controle completo sobre os argumentos que um usuário pode transmitir a ela.
Exceções de C++ versus extensões do Windows SEH
Os programas C e C++ podem usar o mecanismo SEH (tratamento de exceções estruturado) no sistema operacional Windows. Os conceitos no SEH lembram os conceitos de exceções do C++, exceto que o SEH usa as construções __try, __except e __finally em vez de try e catch. No Visual C++, as exceções são implementadas para SEH. No entanto, ao escrever o código do C++, use a sintaxe de exceção do C++.
Para obter mais informações sobre SEH, consulte Tratamento de exceções estruturado (C/C++).
Especificações de exceção e noexcept
As especificações de exceção foram introduzidas no C++ como uma maneira de especificar as exceções que uma função pode lançar. No entanto, as especificações de exceção foram comprovadas como problemáticas na prática e foram substituídas no padrão de rascunho do C++11. Recomendamos que você não use especificações de exceção para a exceção de throw(), que indica que a exceção não permite que nenhuma exceção escape. Caso você precise usar especificações de exceção do tipo throw(type), saiba que Visual C++ foge do padrão em determinados aspectos. Para obter mais informações, consulte Especificações de exceção. O especificador noexcept foi introduzido no C++11 como a alternativa preferencial a throw().
Consulte também
Conceitos
Como realizar a interface entre códigos excepcional e não excepcional