C ++ 2aのエラー処理はどうなりますか

画像


数週間前は、C ++の世界での主要なカンファレンスであるCPPCONでした
午前8時から午後10時までの5日間連続して報告がありました。 すべての信仰を持つプログラマーは、C ++の将来、有毒なバイクについて議論し、C ++を簡単にする方法を考えました。


驚くべきことに、多くのレポートがエラー処理に当てられていました。 確立されたアプローチでは、最大のパフォーマンスを達成したり、コードシートを生成したりすることはできません。
C ++ 2aではどのような革新が待っていますか?


理論のビット


従来、プログラム内のすべてのエラー状況は、2つの大きなグループに分けることができます。



致命的なエラー


その後、実行を継続することは意味がありません。
たとえば、これはNULLポインターの逆参照、メモリの通過、0での除算、またはコード内の他の不変条件への違反です。 問題が発生したときに行う必要があるのは、問題に関する最大限の情報を提供し、プログラムを完了することです。


C ++で 多すぎる プログラムを完了するにはすでに十分な方法があります。



ライブラリは、クラッシュに関するデータを収集し始めているようです( 1、2、3 )。


致命的でないエラー


これらは、プログラムのロジックによって提供されるエラーです。 たとえば、ネットワークでの作業中のエラー、無効な文字列の数値への変換など。 プログラムでのこのようなエラーの出現は、物事の順序です。 その処理については、C ++で一般的に受け入れられているいくつかの戦術があります。
簡単な例を使用して、それらについて詳しく説明します。


エラー処理のさまざまなアプローチを使用して、 void addTwo()関数を記述してみましょう。
関数は2行を読み取り、それらをintに変換して合計を出力します。 IOエラー、オーバーフロー、および数値への変換を処理する必要があります。 面白くない実装の詳細は省略します。 3つの主なアプローチを検討します。


1.例外


 //     //   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; } } 

C ++の例外を使用すると 不要な なくてもエラーを集中的に処理できます。
しかし、これには多くの問題が発生します。



2.戻りコード


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; } 

よく見えませんか?


  1. 関数の実際の値を返すことはできません。
  2. エラーの処理を忘れることは非常に簡単です(printfの戻りコードを最後にチェックしたのはいつですか?)。
  3. 各関数の横にエラー処理コードを記述する必要があります。 そのようなコードは読みにくいです。
    C ++ 17およびC ++ 2aを使用すると、これらの問題がすべて順番に修正されます。

3. C ++ 17およびnodiscard


nodiscard C ++ 17で nodiscardました。
関数宣言の前に指定すると、戻り値のチェックが行われないため、コンパイラの警告が発生します。


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

クラス、構造体、または列挙クラスにnodiscardを指定することもできます。
この場合、属性アクションはnodiscardというラベルのタイプの値を返すすべての関数に拡張されnodiscard


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

nodiscardコードを提供しません。


C ++ 17 std ::オプション


C ++ 17では、 std::optional<T>
コードがどのように見えるか見てみましょう。


 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; } 

関数からin-out引数を削除すると、コードがよりきれいになります。
ただし、エラー情報は失われています。 いつ、何が悪かったのかが不明確になりました。
std::optionalstd::variant<ResultType, ValueType>置き換えることができます。
コードの意味は、 std::optionalと同じstd::optionalが、より面倒です。


C ++ 2aおよびstd ::予想


std::expected<ResultType, ErrorType> - 特別なテンプレートタイプで、最も近い不完全な標準に分類される場合があります。
2つのパラメーターがあります。



これは通常のvariantとどう違うのですか? 何が特別なのですか?
std::expectedされるのはモナドです。
モナドのようにstd::expectedれるstd::expected catch_errorの操作をサポートすることが提案std::expectedれています: mapcatch_errorbindunwrapreturnそしてthen
これらの関数を使用して、関数呼び出しをチェーンにチェーンできます。


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

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); 

以下は擬似コードのみであり、最新のコンパイラで強制的に動作させることはできません。
Haskellからモナドの操作記録するための構文を借用することができます 。 許可しないのはなぜですか:


 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) } 

一部の著者はこの構文を提案しています:


 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; } 

コンパイラは、このようなコードブロックを関数呼び出しのシーケンスに自動的に変換します。 ある時点で、関数が期待したものを返さない場合、計算チェーンが中断します。 また、エラータイプとして、標準に既に存在する例外タイプを使用できますstd::out_of_range std::runtime_errorstd::out_of_rangeなど。


構文をうまく設計できれば、 std::expectedは単純で効率的なコードを書くことができます。


おわりに


エラーを処理する理想的な方法はありません。 最近まで、C ++には、モナドを除くほとんどすべてのエラー処理方法がありました。
C ++ 2aでは、考えられるすべてのメソッドが表示される可能性があります。


トピックで何を読んで見るか


  1. 実際の提案
  2. CPPCONで予想されるstd ::に関するスピーチ
  3. Andrei Alexandrescu std :: about C ++ Russiaで期待されています。
  4. Redditの提案に関する最近の議論


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


All Articles