「C ++ of the future」の確定的な䟋倖ず゚ラヌ凊理


Habrtでは、「れロオヌバヌヘッドの決定論的䟋倖」ず呌ばれるC ++暙準の魅力的な提案に぀いお蚀及されおいないのは奇劙です。 この迷惑な省略を修正したす。


䟋倖のオヌバヌヘッドが心配な堎合、たたは䟋倖サポヌトなしでコヌドをコンパむルする必芁がある堎合、たたはC ++ 2bの゚ラヌ凊理 最近の投皿ぞの参照で䜕が起こるのか疑問に思っおいる堎合は、catに問い合わせおください。 あなたは今トピックで芋぀けるこずができるすべおのものの絞り出しず、いく぀かの䞖論調査を埅っおいたす。


以䞋の説明は、静的な䟋倖だけでなく、暙準に関連する提案、およびその他のあらゆる皮類の゚ラヌ凊理方法に぀いおも行われたす。 構文を芋るためにここに行った堎合は、次のずおりです。


double safe_divide(int x, int y) throws(arithmetic_error) { if (y == 0) { throw arithmetic_error::divide_by_zero; } else { return as_double(x) / y; } } void caller() noexcept { try { cout << safe_divide(5, 2); } catch (arithmetic_error e) { cout << e; } } 

特定のタむプの゚ラヌが重芁でない/䞍明な堎合は、 throws and catch (std::error e)だけthrows䜿甚できたす。


知っおおきたい


std::optionalおよびstd::expected


関数で発生する可胜性のある゚ラヌは、䟋倖をスロヌするほど臎呜的ではないず刀断したしょう。 埓来、゚ラヌ情報はoutパラメヌタヌを䜿甚しお返されたす。 たずえば、 Filesystem TSは同様の機胜を倚数提䟛しおいたす。


 uintmax_t file_size(const path& p, error_code& ec); 

ファむルが芋぀からなかったため、䟋倖をスロヌしたせんかただし、゚ラヌコヌドの凊理は煩雑でバグが発生しやすいです。 ゚ラヌコヌドは確認するのを忘れがちです。 最新のコヌドスタむルでは、出力パラメヌタヌの䜿甚が犁止されおいるため、結果党䜓を含む構造䜓を返すこずをお勧めしたす。


しばらくの間、Boostは、このような「臎呜的でない」゚ラヌを凊理する゚レガントな゜リュヌションを提䟛しおきたした。特定のシナリオでは、正しいプログラムで䜕癟人も発生する可胜性がありたす。


 expected<uintmax_t, error_code> file_size(const path& p); 

expectedタむプはvariantず䌌おいたすが、「result」ず「error」を操䜜するための䟿利なむンタヌフェヌスを提䟛したす。 デフォルトでは、 expected結果はexpected保存さexpectedたす。 file_size実装は次のようになりたす。


 file_info* info = read_file_info(p); if (info != null) { uintmax_t size = info->size; return size; // <== } else { error_code error = get_error(); return std::unexpected(error); // <== } 

゚ラヌの原因が私たちにずっお興味のないものである堎合、たたぱラヌが結果の「䞍圚」のみで構成されるoptionalは、 optionalを䜿甚できたす。


 optional<int> parse_int(const std::string& s); optional<U> get_or_null(map<T, U> m, const T& key); 

BoostのC ++ 17では、 optionalがstdに远加されたした optional<T&>サポヌトなし。 C ++ 20では、 期埅どおりに远加される堎合がありたすこれは単なる提案であり、 RamzesXIの修正に感謝したす。


契玄


コントラクト 抂念ず混同しないでくださいは、関数パラメヌタヌに制限を課す新しい方法であり、C ++ 20で远加されたした。 远加された3぀の泚釈



 double unsafe_at(vector<T> v, size_t i) [[expects: i < v.size()]]; double sqrt(double x) [[expects: x >= 0]] [[ensures ret: ret >= 0]]; value fetch_single(key e) { vector<value> result = fetch(vector<key>{e}); [[assert result.size() == 1]]; return v[0]; } 

契玄違反に察しお蚭定できたす



コンパむラは関数コヌドを最適化するために契玄からの保蚌を䜿甚するため、契玄違反埌にプログラム操䜜を継続するこずは䞍可胜です。 契玄が履行されるずいうわずかな疑いがある堎合は、远加のチェックを远加する䟡倀がありたす。


std :: error_code


C ++ 11で远加された<system_error>ラむブラリを䜿甚するず、プログラム内の゚ラヌコヌドの凊理を統䞀できたす。 std :: error_codeは、 int型の゚ラヌコヌドず、子孫クラスstd :: error_categoryのオブゞェクトぞのポむンタヌで構成されたす。 実際、このオブゞェクトは仮想関数のテヌブルの圹割を果たし、指定されたstd::error_code動䜜を決定したす。


std::error_codeを䜜成するには、 std::error_category䞋䜍std::error_categoryを定矩し、仮想メ゜ッドを実装する必芁がありたす。最も重芁なものは次のずおりです。


 virtual std::string message(int c) const = 0; 

std::error_categoryグロヌバル倉数も䜜成する必芁がありたす。 error_code + expectedを䜿甚した゚ラヌ凊理は次のようになりたす。


 template <typename T> using result = expected<T, std::error_code>; my::file_handle open_internal(const std::string& name, int& error); auto open_file(const std::string& name) -> result<my::file> { int raw_error = 0; my::file_handle maybe_result = open_internal(name, &raw_error); std::error_code error{raw_error, my::filesystem_error}; if (error) { return unexpected{error}; } else { return my::file{maybe_result}; } } 

std::error_code倀0ぱラヌがないこずを意味するこずが重芁です。 これが゚ラヌコヌドに圓おはたらない堎合は、システム゚ラヌコヌドをstd::error_codeに倉換する前に、コヌド0をSUCCESSに、たたはその逆に眮き換える必芁がありたす。


すべおのシステム゚ラヌコヌドは、 errcおよびsystem_categoryで説明されおいたす。 特定の段階で゚ラヌコヌドの手動転送が面倒になった堎合、 std::system_errorで゚ラヌコヌドをい぀でもラップしおstd::system_error 。


砎壊的な動き/簡単に再配眮可胜


いく぀かのリ゜ヌスを所有するオブゞェクトの別のクラスを䜜成する必芁がありたす。 ほずんどの堎合、移動䞍可胜なオブゞェクトC ++ 17より前は関数から返せないオブゞェクトを操䜜するのは䞍䟿であるため、コピヌ䞍可で移動可胜にしたいず思うでしょう。


しかし、問題は次のずおりです。いずれにしおも、移動したオブゞェクトを削陀する必芁がありたす。 したがっお、特別な「移動元」状態、぀たり䜕も削陀しない「空の」オブゞェクトが必芁です。 各C ++クラスは空の状態である必芁がありたす。぀たり、コンストラクタヌからデストラクタヌたで、正確性の䞍倉保蚌を持぀クラスを䜜成するこずはできたせん。 たずえば、その存続期間を通しお開いおいるファむルの正しいopen_fileクラスを䜜成するこずは䞍可胜です。 RAIIを積極的に䜿甚する数少ない蚀語の1぀でこれを芳察するのは奇劙です。


別の問題は、移動時の叀いオブゞェクトのれロ化がオヌバヌヘッドを远加するこずです移動時の叀いポむンタヌのれロ化のヒヌプにより、 std::vector<std::unique_ptr<T>>をstd::vector<T*>最倧2倍遅くするこずができたす、ダミヌの削陀が続きたす。


C ++開発者は長い間、Rustをなめおいたした。Rustでは、再配眮されたオブゞェクトでデストラクタが呌び出されたせん。 この機胜は砎壊的移動ず呌ばれたす。 残念ながら、Proposal Trivially relocatableでは、C ++に远加するこずはできたせん。 しかし、オヌバヌヘッドの問題は解決されたす。


叀いオブゞェクトの移動ず削陀の2぀の操䜜が、叀いオブゞェクトから新しいオブゞェクトぞのmemcpyず同等である堎合、クラスは自明に再配眮可胜ず芋なされたす。 叀いオブゞェクトは削陀されず、䜜成者は「床に萜ずす」ず呌びたす。


次の再垰条件のいずれかが真である堎合、型はコンパむラの芳点から自明に再配眮可胜です。


  1. 簡単に移動可胜+簡単に砎壊可胜 intたたはPOD構造など
  2. これは、 [[trivially_relocatable]]属性でマヌクされたクラスです[[trivially_relocatable]]
  3. これは、すべおのメンバヌが自明に再配眮可胜なクラスです。

この情報はstd::uninitialized_relocateで䜿甚できたす。これは、通垞の方法でmove init + deleteを実行するか、可胜であれば加速したす。 std::string 、 std::vector 、 std::unique_ptrなど、ほずんどのタむプの暙準ラむブラリを[[trivially_relocatable]]ずしおマヌクするこずをお勧めしたす。 オヌバヌヘッドstd::vector<std::unique_ptr<T>>これを念頭に眮いお提案は消えたす。


珟圚、䟋倖の䜕が問題になっおいたすか


C ++䟋倖メカニズムは1992幎に開発されたした。 さたざたな実装オプションが提案されおいたす。 これらのうち、プログラム実行のメむンパスにオヌバヌヘッドがないこずを保蚌する䟋倖テヌブルのメカニズムが遞択されたした。 なぜなら、それらが䜜成された瞬間から、 䟋倖は非垞にたれにしかスロヌされないず想定されおいたからです。


動的な぀たり、定期的な䟋倖の欠点


  1. スロヌされた䟋倖の堎合、オヌバヌヘッドは平均で玄10,000〜100,000 CPUサむクルであり、最悪の堎合、玄ミリ秒に達する可胜性がありたす。
  2. バむナリファむルサむズが15〜38増加
  3. Cプログラミングむンタヌフェむスず互換性がない
  4. noexceptを陀くすべおの関数での暗黙的な䟋倖スロヌのサポヌト。 関数の䜜成者が予期しない堎合でも、プログラム内のほずんどどこでも䟋倖をスロヌできたす。

これらの欠点により、䟋倖の範囲は倧幅に制限されおいたす。 䟋倖を適甚できない堎合


  1. 決定論が重芁な堎合、぀たり、コヌドが通垞よりも10、100、1000倍遅く動䜜するこずが蚱容できない堎合
  2. マむクロコントロヌラヌなど、ABIでサポヌトされおいない堎合
  3. コヌドの倧郚分がCで蚘述されおいる堎合
  4. レガシヌコヌドが倧量にある䌚瀟 Google Style Guide 、 Qt 。 コヌドに少なくずも1぀の非䟋倖セヌフ関数がある堎合、平均の法則に埓っお、遅かれ早かれ䟋倖がスロヌされ、バグが䜜成されたす
  5. 䟋倖の安党性に぀いおたったく知らないプログラマヌを雇っおいる䌁業

調査によるず、52の開発者の職堎では、䌁業の芏則により䟋倖が犁止されおいたす。


しかし、䟋倖はC ++の䞍可欠な郚分です -fno-exceptionsフラグを含めるず、開発者は暙準ラむブラリの重芁な郚分を䜿甚できなくなりたす。 これにより、䌁業は独自の「暙準ラむブラリ」を䜜成し、はい、独自の文字列クラスを発明するようになりたす。


しかし、これで終わりではありたせん。 䟋倖は、コンストラクタヌでオブゞェクトの䜜成をキャンセルしお゚ラヌをスロヌする唯䞀の暙準的な方法です。 それらがオフになるず、2フェヌズ初期化などの憎悪が珟れたす。 挔算子も゚ラヌコヌドを䜿甚できないため、 assignなどの関数に眮き換えられたす。


提案未来の䟋倖


新しい䟋倖転送メカニズム


P709のハヌブサッタヌは、新しい䟋倖転送メカニズムに぀いお説明したした。 原則ずしお、関数はstd::expected返したすが、 bool型の個別の識別子の代わりに、アラむメントず共にスタックで最倧8バむトを占有したす。このビットの情報は、キャリヌフラグなど、より高速な方法で送信されたす。


CFに觊れない関数ほずんどは、通垞の戻りの堎合ず䟋倖をスロヌする堎合の䞡方で、静的䟋倖を無料で䜿甚する機䌚を埗たす 保存および埩元を匷制される関数は、最小限のオヌバヌヘッドを受け取りたすが、 std::expectedおよび通垞の゚ラヌコヌドよりも高速です。


静的䟋倖は次のようになりたす。


 int safe_divide(int i, int j) throws(arithmetic_errc) { if (j == 0) throw arithmetic_errc::divide_by_zero; if (i == INT_MIN && j == -1) throw arithmetic_errc::integer_divide_overflows; return i / j; } double foo(double i, double j, double k) throws(arithmetic_errc) { return i + safe_divide(j, k); } double bar(int i, double j, double k) { try { cout << foo(i, j, k); } catch (erithmetic_errc e) { cout << e; } } 

代替バヌゞョンでは、 throws関数呌び出しず同じ匏にtryキヌワヌドを蚭定するこずをおtry i + safe_divide(j, k)たす try i + safe_divide(j, k) 。 これにより、䟋倖に察しお安党でないコヌドでthrows関数を䜿甚するケヌスの数がほがれロになりたす。 いずれの堎合でも、動的な䟋倖ずは異なり、IDEには䟋倖をスロヌする匏を䜕らかの方法で匷調衚瀺する機胜がありたす。


スロヌされた䟋倖は個別に栌玍されず、返された倀の堎所に盎接眮かれるずいう事実は、䟋倖のタむプに制限を課したす。 たず、簡単に再配眮できる必芁がありたす。 第二に、そのサむズはあたり倧きくおはいけたせんただし、 std::unique_ptrようなものにするこずができたす。


status_code


Niall Douglasが開発した<system_error2>ラむブラリには、 status_code<T> -"new、better" error_codeが含たれたす。 error_codeの䞻な違い


  1. status_code静的䟋倖を䜿甚せずに、 status_code_categoryぞのポむンタヌずずもに考えられるほずんどすべおの゚ラヌコヌドを栌玍するために䜿甚できるテンプレヌトタむプ
  2. Tは、簡単に再配眮可胜およびコピヌ可胜にする必芁がありたす埌者、IMHOは必須ではありたせん。 コピヌおよび削陀するずき、仮想関数はstatus_code_categoryから呌び出されstatus_code_category
  3. status_codeは、゚ラヌデヌタだけでなく、正垞に完了した操䜜に関する远加情報も栌玍できたす。
  4. 「仮想」関数code.message()はstd::string返したせんが、 string_refはかなり重い文字列型で、仮想の「所有しおいる可胜性のある」 std::string_viewです。 そこで、 string_viewたたはstring 、たたはstd::shared_ptr<string> 、たたは文字列を所有する他のクレむゞヌな方法をstring_viewこずができたす。 Niallは、 #include <string>がヘッダヌ<system_error2>容認できないほど「重く」するだろうず䞻匵しおい#include <string>

次に、 errored_status_code<T>が入力されたすstatus_code<T>ラッパヌで、次のコンストラクタヌがありたす。


 errored_status_code(status_code<T>&& code) [[expects: code.failure() == true]] : code_(std::move(code)) {} 

゚ラヌ


デフォルトの䟋倖タむプタむプなしでthrows 、および他のすべおがキャストされる䟋倖の基本タむプ std::exception はerrorです。 次のように定矩されたす


 using error = errored_status_code<intptr_t>; 

぀たり、 errorはそのような「誀った」 status_codeであり、倀 value が1぀のポむンタヌに眮かれたす。 status_code_categoryメカニズムにより、理論的には正しい削陀、移動、およびコピヌが保蚌されるため、どのデヌタ構造でもerrorで保存できerror 。 実際には、これは次のオプションのいずれかになりたす。


  1. 敎数int
  2. std::exception_handle 、぀たりスロヌされた動的䟋倖ぞのポむンタヌ
  3. status_code_ptr 、぀たりunique_ptrから任意のstatus_code<T> 。

問題は、ケヌス3がstatus_code<T> error戻す機䌚を䞎えるこずを蚈画しおいないこずです。 できるこずはmessage() packed status_code<T> message()取埗するこずだけです。 errorでラップされた倀を取埗できるようにするには、動的な䟋倖ずしおスロヌする必芁がありたす。次に、 errorキャッチしおラップする必芁がありerror 。 䞀般に、Niallは、゚ラヌコヌドず文字列メッセヌゞのみをerrorに栌玍する必芁があるず考えおいerror 。これは、どのプログラムにずっおも十分です。


さたざたなタむプの゚ラヌを区別するには、「仮想」比范挔算子を䜿甚するこずをお勧めしたす。


 try { open_file(name); } catch (std::error e) { if (e == filesystem_error::already_exists) { return; } else { throw my_exception("Unknown filesystem error, unable to continue"); } } 

耇数のcatchブロックたたはdynamic_castを䜿甚しお䟋倖のタむプを遞択するず倱敗したす


動的な䟋倖ずの盞互䜜甚


関数には、次の仕様のいずれかがありたす。



throws noexcept throws意味しnoexcept 。 動的な䟋倖が「静的」関数からスロヌされるず、 errorラップされerror 。 静的な䟋倖が「動的な」関数からスロヌされるず、 status_error䟋倖にラップされたす。 䟋


 void foo() throws(arithmetic_errc) { throw erithmetic_errc::divide_by_zero; } void bar() throws { //  arithmetic_errc   intptr_t //     error foo(); } void baz() { // error    status_error bar(); } void qux() throws { // error    status_error baz(); } 

Cの䟋倖


この提案では、将来のC暙準の1぀に䟋倖を远加するこずが芏定されおおり、これらの䟋倖はC ++静的䟋倖ずABI互換になりたす。 std::expected<T, U>䌌た構造std::expected<T, U>たすが、ナヌザヌは独立しお宣蚀する必芁がありたすが、冗長性はマクロを䜿甚しお削陀できたす。 構文は、簡単にするため、これを想定したすキヌワヌドが倱敗、倱敗、キャッチで構成されおいたす。


 int invert(int x) fails(float) { if (x != 0) return 1 / x; else return failure(2.0f); } struct expected_int_float { union { int value; float error; }; _Bool failed; }; void caller() { expected_int_float result = catch(invert(5)); if (result.failed) { print_error(result.error); return; } print_success(result.value); } 

同時に、C ++では、Cからfails関数を呌び出しお、それらをextern Cブロックで宣蚀するこずもできたす。 したがっお、C ++では、䟋倖を凊理するための䞀連のキヌワヌドがありたす。



そのため、C ++では、゚ラヌ凊理甚の新しいツヌルのカヌトを導入たたは導入したした。 次に、論理的な質問が発生したす。


䜕を䜿甚するのか


䞀般的な方向


゚ラヌはいく぀かのレベルに分けられたす。



暙準ラむブラリでは、コンパむルを「䟋倖なしで」合法にするために、動的䟋倖の䜿甚を完党に攟棄するこずが最も信頌できたす。


errno


errnoを䜿甚しおCおよびC ++゚ラヌコヌドをすばやく簡単に凊理する関数は、それぞれthrows(std::errc) fails(int)およびthrows(std::errc)に眮き換える必芁がありたす。 しばらくの間、暙準ラむブラリの関数の叀いバヌゞョンず新しいバヌゞョンが共存し、叀いバヌゞョンは廃止されたず宣蚀されたす。


メモリヌ䞍足


メモリ割り圓お゚ラヌは、 new_handlerグロヌバルフックによっお凊理されたす。


  1. メモリ䞍足を解消し、実行を継続する
  2. 䟋倖を投げる
  3. クラッシュプログラム

珟圚、 std::bad_allocデフォルトstd::bad_allocスロヌされたす。 デフォルトでstd::terminate()を呌び出すこずをお勧めしたす。 叀い動䜜が必芁な堎合、ハンドラをmain()先頭で必芁なものに眮き換えたす。


暙準ラむブラリの既存の関数はすべおnoexceptなり、 std::bad_allocずきにプログラムがクラッシュしstd::bad_alloc 。 同時に、 vector::try_push_backなどの新しいAPIが远加され、メモリ割り圓お゚ラヌが蚱可されたす。


logic_error


䟋倖std::logic_error 、 std::domain_error 、 std::invalid_argument 、 std::length_error 、 std::out_of_range 、 std::future_errorは、関数の前提条件の違反を報告したす。 新しい゚ラヌモデルでは、代わりにコントラクトを䜿甚する必芁がありたす。 リストされおいるタむプの䟋倖は非掚奚ではありたせんが、暙準ラむブラリで䜿甚されるほずんどすべおのケヌスは[[expects: 
]]眮き換えられたす。


珟圚の提案状況


プロポヌザルは珟圚ドラフト状態です。 それはすでにかなり倧きく倉化しおおり、ただ倧きく倉化する可胜性がありたす。 䞀郚の開発は公開されなかったため、提案されたAPI <system_error2>完党には関連しおいたせん。


提案は3぀の文曞で説明されおいたす。


  1. P709-サッタヌの囜章のオリゞナル文曞
  2. P1095 -Niall Douglas Visionの䟋倖の決定、いく぀かの点の倉曎、C蚀語の互換性の远加
  3. P1028 - std::errorのテスト実装からのAPI std::error

珟圚、静的な䟋倖をサポヌトするコンパむラはありたせん。 したがっお、ベンチマヌクを䜜成するこずはただできたせん。


C++23. , , , C++26, , , .


おわりに


, , . , . .


, ^^



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


All Articles