O que acontecerá com o tratamento de erros no C ++ 2a

imagem


Algumas semanas atrás, foi a principal conferência no mundo C ++ - CPPCON .
Cinco dias consecutivos das 8h às 22h, houve relatos. Programadores de todas as religiões discutiram o futuro do C ++, bicicletas envenenadas e pensaram em como tornar o C ++ mais fácil.


Surpreendentemente, muitos relatórios foram dedicados ao tratamento de erros. Abordagens bem estabelecidas não permitem atingir o desempenho máximo ou podem gerar folhas de código.
Quais inovações nos esperam em C ++ 2a?


Pouco de teoria


Convencionalmente, todas as situações errôneas do programa podem ser divididas em 2 grandes grupos:


  • Erros fatais.
  • Erros não fatais ou esperados.

Erros fatais


Depois deles, não faz sentido continuar a execução.
Por exemplo, isso está desreferenciando um ponteiro nulo, passando pela memória, dividindo por 0 ou violando outros invariantes no código. Tudo o que precisa ser feito quando ocorrerem é fornecer o máximo de informações sobre o problema e concluir o programa.


Em c ++ demais já existem maneiras suficientes de concluir o programa:



As bibliotecas estão começando a aparecer para coletar dados sobre falhas ( 1 , 2 , 3 ).


Erros não fatais


Esses são erros fornecidos pela lógica do programa. Por exemplo, erros ao trabalhar com a rede, converter uma sequência inválida em um número, etc. A aparência de tais erros no programa está na ordem das coisas. Para o processamento, existem várias táticas geralmente aceitas em C ++.
Vamos falar sobre eles em mais detalhes usando um exemplo simples:


Vamos tentar escrever uma função void addTwo() usando abordagens diferentes para o tratamento de erros.
A função deve ler 2 linhas, convertê-las em int e imprimir a soma. Precisa lidar com erros de IO, estouro e conversão para número. Omitirei detalhes de implementação desinteressantes. Vamos considerar três abordagens principais.


1. Exceções


 //     //   IO  std::runtime_error std::string readLine(); //    int //     std::invalid_argument int parseInt(const std::string& str); //  a  b //     std::overflow_error int safeAdd(int a, int b); void addTwo() { try { std::string aStr = readLine(); std::string bStr = readLine(); int a = parseInt(aStr); int b = parseInt(bStr); std::cout << safeAdd(a, b) << std::endl; } catch(const std::exeption& e) { std::cout << e.what() << std::endl; } } 

Exceções em C ++ permitem que você lide com erros centralmente sem desnecessário ,
mas você tem que pagar por isso com um monte de problemas.


  • a sobrecarga envolvida no tratamento de exceções é bastante grande; muitas vezes você não pode lançar exceções.
  • é melhor não lançar exceções de construtores / destruidores e observar RAII.
  • pela assinatura da função, é impossível entender qual exceção pode sair da função.
  • o tamanho do arquivo binário aumenta devido a um código de suporte de exceção adicional.

2. Códigos de retorno


Abordagem clássica herdada de C.


 bool readLine(std::string& str); bool parseInt(const std::string& str, int& result); bool safeAdd(int a, int b, int& result); void processError(); void addTwo() { std::string aStr; int ok = readLine(aStr); if (!ok) { processError(); return; } std::string bStr; ok = readLine(bStr); if (!ok) { processError(); return; } int a = 0; ok = parseInt(aStr, a); if (!ok) { processError(); return; } int b = 0; ok = parseInt(bStr, b); if (!ok) { processError(); return; } int result = 0; ok = safeAdd(a, b, result); if (!ok) { processError(); return; } std::cout << result << std::endl; } 

Não parece muito bom?


  1. Você não pode retornar o valor real de uma função.
  2. É muito fácil esquecer o tratamento do erro (na última vez em que você verificou o código de retorno de printf?).
  3. Você deve escrever o código de tratamento de erros ao lado de cada função. Esse código é mais difícil de ler.
    Usar C ++ 17 e C ++ 2a corrigirá todos esses problemas em sequência.

3. C ++ 17 e nodiscard


O nodiscard no C ++ 17.
Se você o especificar antes da declaração da função, a ausência de verificação do valor de retorno causará um aviso do compilador.


 [[nodiscard]] bool doStuff(); /* ... */ doStuff(); //  ! bool ok = doStuff(); // . 

Você também pode especificar o nodiscard para uma classe, estrutura ou classe de enumeração.
Nesse caso, a ação do atributo se estende a todas as funções que retornam valores do tipo rotulado nodiscard .


 enum class [[nodiscard]] ErrorCode { Exists, PermissionDenied }; ErrorCode createDir(); /* ... */ createDir(); 

Não fornecerei código com nodiscard .


C ++ 17 std :: opcional


No C ++ 17, std::optional<T> .
Vamos ver como o código fica agora.


 std::optional<std::string> readLine(); std::optional<int> parseInt(const std::string& str); std::optional<int> safeAdd(int a, int b); void addTwo() { std::optional<std::string> aStr = readLine(); std::optional<std::string> bStr = readLine(); if (aStr == std::nullopt || bStr == std::nullopt){ std::cerr << "Some input error" << std::endl; return; } std::optional<int> a = parseInt(*aStr); std::optional<int> b = parseInt(*bStr); if (!a || !b) { std::cerr << "Some parse error" << std::endl; return; } std::optional<int> result = safeAdd(*a, *b); if (!result) { std::cerr << "Integer overflow" << std::endl; return; } std::cout << *result << std::endl; } 

Você pode remover argumentos de entrada e saída das funções e o código ficará mais limpo.
No entanto, estamos perdendo informações de erro. Não ficou claro quando e o que deu errado.
Você pode substituir std::optional por std::variant<ResultType, ValueType> .
O significado do código é o mesmo que com std::optional , mas mais complicado.


C ++ 2a e std :: esperado


std::expected<ResultType, ErrorType> - um tipo de modelo especial , provavelmente cairá no padrão incompleto mais próximo.
Possui 2 parâmetros.


  • ReusltType é o valor esperado.
  • ErrorType - tipo de erro.
    std::expected pode conter o valor esperado ou um erro. Trabalhar com esse tipo será mais ou menos assim:
     std::expected<int, string> ok = 0; expected<int, string> notOk = std::make_unexpected("something wrong"); 

Como isso difere da variant usual? O que o torna especial?
std::expected será uma mônada .
É proposto oferecer suporte a um monte de operações no std::expected catch_error como em uma mônada: map , catch_error , bind , unwrap , return e then .
Usando essas funções, você pode encadear chamadas de função em uma cadeia.


 getInt().map([](int i)return i * 2;) .map(integer_divide_by_2) .catch_error([](auto e) return 0; ); 

Suponha que tenhamos funções com o retorno de std::expected .


 std::expected<std::string, std::runtime_error> readLine(); std::expected<int, std::runtime_error> parseInt(const std::string& str); std::expected<int, std::runtime_error> safeAdd(int a, int b); 

Abaixo está apenas o pseudo-código; ele não pode ser forçado a funcionar em nenhum compilador moderno.
Você pode tentar emprestar da Haskell a sintaxe do para operações de gravação em mônadas. Por que não permitir:


 std::expected<int, std::runtime_error> result = do { auto aStr <- readLine(); auto bStr <- readLine(); auto a <- parseInt(aStr); auto b <- parseInt(bStr); return safeAdd(a, b) } 

Alguns autores sugerem esta sintaxe:


 try { auto aStr = try readLine(); auto bStr = try readLine(); auto a = try parseInt(aStr); auto b = try parseInt(bStr); std::cout result << std::endl; return safeAdd(a, b) } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; return 0; } 

O compilador converte automaticamente esse bloco de código em uma sequência de chamadas de função. Se em algum momento a função retornar não o que é esperado dela, a cadeia de computação será interrompida. Sim, e como tipo de erro, você pode usar os tipos de exceção já existentes no padrão: std::runtime_error , std::out_of_range , etc.


Se você pode projetar bem a sintaxe, então std::expected permitirá que você escreva um código simples e eficiente.


Conclusão


Não existe uma maneira ideal de lidar com erros. Até recentemente, em C ++ havia quase todos os métodos possíveis de manipulação de erros, exceto mônadas.
No C ++ 2a, todos os métodos possíveis provavelmente aparecerão.


O que ler e ver no tópico


  1. Proposta real .
  2. Discurso sobre std :: esperado com CPPCON .
  3. Andrei Alexandrescu sobre std :: esperado em C ++ na Rússia .
  4. Discussão mais ou menos recente da proposta no Reddit .

Source: https://habr.com/ru/post/pt426965/


All Articles