Estou codificando uma pequena biblioteca e estou tendo problemas para projetar o tratamento de exceções. Devo dizer que estou (ainda) confuso com esse recurso da linguagem C ++ e tentei ler o máximo possível sobre o assunto para entender o que eu teria que fazer para trabalhar adequadamente com classes de exceção.
Decidi usar um system_error
tipo de abordagem inspirado na implementação do STL da future_error
classe.
Eu tenho uma enumeração que contém os códigos de erro:
enum class my_errc : int
{
error_x = 100,
error_z = 101,
error_y = 102
};
e uma única classe de exceção (suportada por um error_category
tipo de estruturas e tudo o mais necessário pelo system_error
modelo):
// error category implementation
class my_error_category_impl : public std::error_category
{
const char* name () const noexcept override
{
return "my_lib";
}
std::string message (int ec) const override
{
std::string msg;
switch (my_errc(ec))
{
case my_errc::error_x:
msg = "Failed 1.";
break;
case my_errc::error_z:
msg = "Failed 2.";
break;
case my_errc::error_y:
msg = "Failed 3.";
break;
default:
msg = "unknown.";
}
return msg;
}
std::error_condition default_error_condition (int ec) const noexcept override
{
return std::error_condition(ec, *this);
}
};
// unique instance of the error category
struct my_category
{
static const std::error_category& instance () noexcept
{
static my_error_category_impl category;
return category;
}
};
// overload for error code creation
inline std::error_code make_error_code (my_errc ec) noexcept
{
return std::error_code(static_cast<int>(ec), my_category::instance());
}
// overload for error condition creation
inline std::error_condition make_error_condition (my_errc ec) noexcept
{
return std::error_condition(static_cast<int>(ec), my_category::instance());
}
/**
* Exception type thrown by the lib.
*/
class my_error : public virtual std::runtime_error
{
public:
explicit my_error (my_errc ec) noexcept :
std::runtime_error("my_namespace ")
, internal_code(make_error_code(ec))
{ }
const char* what () const noexcept override
{
return internal_code.message().c_str();
}
std::error_code code () const noexcept
{
return internal_code;
}
private:
std::error_code internal_code;
};
// specialization for error code enumerations
// must be done in the std namespace
namespace std
{
template <>
struct is_error_code_enum<my_errc> : public true_type { };
}
Eu só tenho um pequeno número de situações nas quais eu lanço exceções ilustradas pela enumeração do código de erro.
O exposto acima não se encaixou bem com um dos meus revisores. Ele era da opinião de que eu deveria ter criado uma hierarquia de classes de exceção com uma classe base derivada std::runtime_error
porque ter o código de erro incorporado na condição mistura coisas - exceções e códigos de erro - e seria mais tedioso lidar com esse ponto. de manuseio; a hierarquia de exceções também permitiria fácil personalização da mensagem de erro.
Um dos meus argumentos era que eu queria simplificá-lo, que minha biblioteca não precisava lançar vários tipos de exceções e que a personalização também é fácil nesse caso, pois é tratada automaticamente - ela error_code
tem um error_category
associado que traduz o código para a mensagem de erro adequada.
Devo dizer que não defendi bem minha escolha, testemunho do fato de que ainda tenho alguns mal-entendidos sobre as exceções de C ++.
Gostaria de saber se meu design faz sentido. Quais seriam as vantagens do outro método sobre o que eu escolhi, pois tenho que admitir que não vejo isso também? O que eu poderia fazer para melhorar?
fonte
Respostas:
Acho que seu colega estava certo: você está projetando seus casos de exceção com base em como é simples implementar dentro da hierarquia, não com base nas necessidades de tratamento de exceções do código do cliente.
Com um tipo de exceção e uma enumeração para a condição de erro (sua solução), se o código do cliente precisar lidar com casos de erro únicos (por exemplo
my_errc::error_x
) , eles deverão escrever um código como este:Com vários tipos de exceção (com uma base comum para toda a hierarquia), você pode escrever:
onde as classes de exceção são assim:
Ao escrever uma biblioteca, o foco deve estar na facilidade de uso, não (necessariamente) na implementação interna.
Você só deve comprometer a facilidade de uso (como será o código do cliente) quando o esforço de fazê-lo corretamente na biblioteca for proibitivo.
fonte
Concordo com seus revisores e com @utnapistim. Você pode usar a
system_error
abordagem ao implementar coisas de plataforma cruzada quando alguns erros requerem tratamento especial. Mas, mesmo neste caso, não é uma boa solução, mas uma solução menos maligna.Mais uma coisa. Ao criar uma hierarquia de exceções, não a torne muito profunda. Crie apenas as classes de exceção que podem ser processadas pelos clientes. Na maioria dos casos, eu uso apenas
std::runtime_error
estd::logic_error
. Atirostd::runtime_error
quando algo sai errado e não consigo fazer nada (o usuário ejeta o dispositivo do computador, ele esqueceu o aplicativo ainda em execução) estd::logic_error
quando a lógica do programa é interrompida (o usuário tenta excluir o registro do banco de dados que não existe, mas antes da operação de exclusão ele pode verificá-lo, para que ele obtenha erro de lógica).E como desenvolvedor de bibliotecas, pense nas necessidades de seus usuários. Tente usá-lo você mesmo e pense se é um conforto para você. Você pode explicar sua posição aos revisores com exemplos de código.
fonte