web-dev-qa-db-ja.com

本当にnoexceptを使うべきなのはいつですか?

noexceptキーワードは多くの関数シグネチャに適切に適用することができますが、実際にいつそれを使用するのか検討すべきかどうかはわかりません。これまで読んだことに基づいて、noexceptを最後に追加することで、移動コンストラクターがスローしたときに発生するいくつかの重要な問題に対処するようです。しかし、そもそもnoexceptについてもっと読むように導いたいくつかの実用的な質問に対して満足のいく答えを提供することはまだできません。

  1. 私が決して投げないであろうということを知っているが、コンパイラーがそれ自身でそのように決定することができない関数の多くの例があります。関数宣言にnoexceptを追加する必要がありますそのような場合は?

    every関数の宣言の後にnoexceptを追加する必要があるかどうかを考えなければならないと、プログラマの生産性が大幅に低下します(率直に言って、お尻の痛みです)。どの状況でnoexceptの使用にもっと注意を払う必要がありますか。また、どの状況で暗黙のnoexcept(false)を回避することができますか?

  2. noexceptを使用した後に実際にパフォーマンスの向上が見られるのはいつ頃なのでしょうか。特に、C++コンパイラがnoexceptを追加した後で、より良いマシンコードを生成できるコードの例を示します。

    個人的には、特定の種類の最適化を安全に適用するためにコンパイラに提供される自由度が増したため、noexceptを気にしています。最近のコンパイラはこのようにnoexceptを利用しますか?そうでなかったら、私はそれらの何人かが近い将来にそうすると期待できるか。

438
void-pointer

実際に使用するのに十分な時間がなかったので、これについて「ベストプラクティス」の答えを出すのは時期尚早だと思います。これらが出てきた直後にスロー指定子について尋ねられたならば、答えは今とは非常に異なるでしょう。

すべての関数宣言の後にnoexceptname__を追加する必要があるかどうかを考えなければならないと、プログラマの生産性が大幅に低下します(そして率直に言って、面倒です)。

関数がスローされないことが明らかな場合は、それを使用してください。

noexceptname__を使用した後に実際にパフォーマンスの向上が見られると期待できるのはいつですか? [...]個人的に、私はnoexceptname__を気にかけています。なぜなら、ある種の最適化を安全に適用するためにコンパイラに提供される自由度が増したからです。

noexceptname__をチェックしてオーバーロードする可能性があるため、最適化の最大の利点はユーザーによる最適化によるもので、コンパイラによるものではないようです。ほとんどのコンパイラは、投げ捨てない例外処理メソッドを採用しているので、コードのマシンコードレベルで大幅に(またはなんでも)変更されることはないでしょう。コード。

ビッグ4(noexceptname__はすでにnoexceptname__なので、デストラクタではなく、デストラクタではなく)でnoexceptname__を使用すると、stdコンテナなどのテンプレートコードでは「よくある」ため、おそらく最良の改善が得られます。例えば、std::vectorは、noexceptname__とマークされていない限り、クラスの移動を使用しません(または、コンパイラーはそれ以外の場合はそれを推定できます)。

163
Pubby

私が最近繰り返し続けているように、セマンティクスが最初

noexceptnoexcept(true)、およびnoexcept(false)を追加することは、まず第一にセマンティクスについてです。それは偶然にもいくつかの可能な最適化を条件付けるだけです。

プログラマがコードを読むとき、noexceptの存在はconstの存在と類似しています。それは、起こり得ることと起こらないことをよりよく理解するのに役立ちます。したがって、関数がスローされるかどうかを知っているかどうかについて考えるのに時間を費やす価値があります。ちなみに、どんな種類の動的メモリ割り当てでもスローされる可能性があります。


それでは、最適化について考えてみましょう。

最も明白な最適化は実際にはライブラリで実行されます。 C++ 11は、関数がnoexceptであるかどうかを知ることを可能にする多くの特性を提供し、可能であれば、標準ライブラリの実装自体が、それらが操作するユーザー定義オブジェクトに対するnoexcept操作を優先するためにそれらの特性を使用します。 のような意味を動かします

コンパイラーは(おそらく)例外処理データからほんの少しの脂肪を削るだけかもしれません、なぜならあなたが嘘をついたかもしれないという事実を考慮に入れるためにを持っているからです。 noexceptとマークされた関数がスローされると、std::terminateが呼び出されます。

これらの意味論は2つの理由で選ばれました:

  • 依存関係がまだ使用していない場合でも、noexceptから即座に利益を得る(後方互換性)
  • 理論的にはスローされるかもしれないが与えられた引数に対して期待されていない関数を呼び出すときにnoexceptの指定を許可する
123
Matthieu M.

これは、実際にはコンパイラのオプティマイザに(潜在的に)大きな違いをもたらします。コンパイラは実際には何年もの間、関数定義の後の空のthrow()文と適切な拡張によってこの機能を持っています。私は現代のコンパイラがより良いコードを生成するためにこの知識を利用することをあなたに保証することができます。

コンパイラでのほとんどすべての最適化は、合法的なものについて推論するために関数の「フローグラフ」と呼ばれるものを使用します。フローグラフは、一般に関数の「ブロック」と呼ばれるもの(1つの入り口と1つの出口を持つコードの領域)とブロック間のエッジで構成され、流れがどこにジャンプできるかを示します。 Noexceptはフローグラフを変更します。

あなたは具体的な例を尋ねました。このコードを見てください:

void foo(int x) {
    try {
        bar();
        x = 5;
        // other stuff which doesn't modify x, but might throw
    } catch(...) {
        // don't modify x
    }

    baz(x); // or other statement using x
}

barnoexceptというラベルが付いている場合、この関数のフローグラフは異なります(barの末尾とcatchステートメントの間で実行する方法はありません)。 noexceptとラベル付けされている場合、コンパイラは、baz関数の実行中にxの値が5であることを確認します - x = 5ブロックは、Edgeのないbaz(x)ブロックをbar()からcatchステートメントまで「支配する」と言われます。それはそれからより効率的なコードを生成するために「定数伝播」と呼ばれる何かをすることができます。ここでbazがインライン化されていれば、xを使ったステートメントは定数を含むかもしれず、実行時評価であったものはコンパイル時評価などに変えることができます。

とにかく、短い答え:noexceptは、コンパイラがより厳密なフローグラフを生成することを可能にし、フローグラフは、あらゆる種類の一般的なコンパイラ最適化について推論するために使用されます。コンパイラにとって、この種のユーザアノテーションは素晴らしいです。コンパイラはこのことを理解しようとしますが、通常はできません(問題の関数がコンパイラから見えない別のオブジェクトファイルにあるか、見えない関数を他動詞として使用している可能性があります)。あなたがそれをnoexceptとして暗示的にラベル付けすることができないようにあなたが気付いていないことさえ投げられるかもしれない些細な例外(例えば、メモリを割り当てることはbad_allocを投げるかもしれません)。

72
Terry Mahaffey

noexceptは、一部の操作のパフォーマンスを劇的に向上させることができます。これはコンパイラによって機械語を生成するレベルでは起こりませんが、最も効果的なアルゴリズムを選択することによって起こります。他の人が述べたように、あなたは関数std::move_if_noexceptを使ってこの選択をします。例えば、std::vectorの増加(例えばreserveと呼ぶとき)は、強力な例外安全性保証を提供しなければなりません。 Tのmoveコンストラクタがスローしないことを知っていれば、すべての要素を移動するだけです。それ以外の場合は、すべてのTをコピーする必要があります。これは この記事 で詳しく説明されています。

49
Andrzej

noexceptname__を使用した後にパフォーマンスの向上を観察する以外に、現実的にできるのはいつですか?特に、C++コンパイラがnoexceptを追加した後でより良いマシンコードを生成できるコードの例を挙げてください。

うーん、絶対?決して時間ではありませんか?しないでください。

noexceptname__は、コンパイラーのパフォーマンス最適化用で、constname__はコンパイラーのパフォーマンス最適化用です。つまり、ほとんどありません。

noexceptname__は主に、関数が例外をスローできるかどうかをコンパイル時に "you"が検出できるようにするために使用されます。覚えておいてください:ほとんどのコンパイラは、実際に何かを投げない限り、例外に対して特別なコードを発行しません。そのため、noexceptname__は、あなたに関数の使用方法に関するヒントを与えるだけではなく、コンパイラに関数の最適化方法に関するヒントを与えるのではありません。

move_if_noexceptのようなテンプレートは、移動コンストラクタがnoexceptname__で定義されているかどうかを検出し、定義されていない場合は型のconst&ではなく&&を返します。安全であれば移動するという言い方です。

一般的に、noexceptname__は、実際には便利になると思われる場合に使用する必要があります。 is_nothrow_constructibleがその型に当てはまる場合、いくつかのコードは異なるパスをたどります。それを実行するコードを使用している場合は、適切なコンストラクタnoexceptname__を使用してください。

手短に言うと、moveコンストラクタや同様のコンストラクトに使用してください。ただし、それに慣れる必要はありません。

30
Nicol Bolas
  1. 私が決して投げないであろうということを知っているが、コンパイラーがそれ自身でそのように決定することができない関数の多くの例があります。そのような場合はすべて、関数宣言にnoexceptを追加する必要がありますか?

noexceptは関数インタフェースの一部なので、扱いにくいです。特にあなたがライブラリを書いているなら、あなたのクライアントコードはnoexceptプロパティに依存することができます。既存のコードを壊す可能性があるため、後で変更するのは難しい場合があります。アプリケーションでのみ使用されるコードを実装している場合は、それほど問題にならないかもしれません。

スローできない関数がある場合は、noexceptをそのまま使用するのか、それとも将来の実装を制限するのかを検討してください。たとえば、(ユニットテストなどで)例外をスローして不正な引数のエラーチェックを導入したい場合や、例外仕様を変更する可能性のある他のライブラリコードに依存している場合があります。その場合、保守的でnoexceptを省略した方が安全です。

一方、関数が決して投げてはならないという確信があり、それが仕様の一部であることが正しい場合は、noexceptと宣言する必要があります。ただし、実装が変更された場合、コンパイラはnoexceptの違反を検出できないことに注意してください。

  1. どの状況でnoexceptの使用にもっと注意を払うべきですか。また、どの状況で暗黙のnoexcept(false)を回避できますか?

最も大きな影響を与える可能性が高いので、集中すべき4つのクラスの関数があります。

  1. 移動操作(移動代入演算子と移動コンストラクタ)
  2. スワップ操作
  3. メモリ割り当て解除(operator delete、operator delete [])
  4. デストラクタ(ただし、これらをnoexcept(true)にしない限り、これらは暗黙的にnoexcept(false)です)

これらの関数は一般的にnoexceptであるべきです、そしてそれはライブラリ実装がnoexceptプロパティを利用することができる可能性が最も高いです。たとえば、std::vectorは、強力な例外保証を犠牲にすることなく、スローされない移動操作を使用できます。それ以外の場合は、要素のコピーにフォールバックする必要があります(C++ 98の場合と同様)。

この種の最適化はアルゴリズムレベルであり、コンパイラの最適化には依存しません。特に要素のコピーが高価な場合は、大きな影響を与える可能性があります。

  1. Noexceptを使用した後に実際にパフォーマンスの向上が見られると期待できるのはいつですか?特に、C++コンパイラがnoexceptを追加した後でより良いマシンコードを生成できるコードの例を挙げてください。

例外を指定しないことやthrow()に対してnoexceptを使用する利点は、スタックの巻き戻しに関して、標準によってコンパイラの自由度が増すことです。 throw()の場合でも、コンパイラはスタックを完全にアンワインドしなければなりません(そしてそれはオブジェクト構造の逆順で行わなければなりません)。

一方、noexceptの場合、これを行う必要はありません。スタックを巻き戻す必要がないという要件はありません(ただし、コンパイラーはそれを許可されています)。この自由度は、スタックをいつでも解くことができるというオーバーヘッドを減らすので、さらなるコード最適化を可能にします。

noexcept、stack unwinding、performance に関する関連質問は、スタックアンワインドが必要な場合のオーバーヘッドについての詳細な説明に入ります。

また、Scott Meyersの著書「Effective Modern C++」、「Item 14:例外が発生しない場合を除いて関数を宣言しない」もお勧めです。

19
Philipp Claßen

Bjarneの言葉では:

終了が受け入れ可能な応答である場合、それがterminate()(13.5.2.5)の呼び出しに変わるので、キャッチされていない例外がそれを達成します。また、noexcept指定子(13.5.1.1)によって、その要求を明示的にすることができます。

成功するフォールトトレラントシステムはマルチレベルです。各レベルは、それ以上歪まずにできる限り多くのエラーに対処し、残りはより高いレベルに任せます。例外はその見方を支持する。さらに、terminate()は、例外処理メカニズム自体が破損している場合、または不完全に使用されている場合は、エスケープを提供することでこのビューをサポートします。同様に、noexceptは、回復を試みることが実行不可能であると思われる場合にエラーを簡単に回避するためのものです。

 double compute(double x) noexcept;   {       
     string s = "Courtney and Anya"; 
     vector<double> tmp(10);      
     // ...   
 }

ベクトルコンストラクタは、その10倍精度のメモリを獲得できず、std::bad_allocをスローします。その場合、プログラムは終了します。それはstd::terminate()(30.4.1.3)を呼び出すことによって無条件に終了します。関数呼び出しからデストラクタを呼び出すことはありません。 thrownoexceptの間のスコープからのデストラクタが(例えばcompute()のsに対して)呼び出されるかどうかは実装定義です。プログラムはもうすぐ終了しようとしているので、とにかくどのオブジェクトにも依存してはいけません。 noexcept指定子を追加することで、コードがthrowに対処するように書かれていないことを示します。

14
Saurav Sahu

私が決して投げないであろうということを知っているが、コンパイラーがそれ自身でそのように決定することができない関数の多くの例があります。そのような場合はすべて、関数宣言にnoexceptを追加する必要がありますか?

"私は彼らが決して投げないことを知っている"と言うとき、あなたはあなたがその関数が投げないことを知っている関数の実装を調べることによって意味します。アプローチは裏返しだと思います。

関数が、関数のdesignの一部であるために例外をスローする可能性があるかどうか、および引数リストと同じくらい重要であり、メソッドがミューテーター(... const)であるかどうかを検討することをお勧めします。 「この関数は決して例外をスローしない」と宣言することは実装上の制約です。省略しても、関数が例外をスローすることはありません。つまり、現在のバージョンの関数and以降のすべてのバージョンでは例外が発生する可能性があります。実装を難しくするのは制約です。しかし、いくつかのメソッドは実用的であるために制約を持たなければなりません。最も重要なことは、デストラクタから呼び出すことができるだけでなく、強力な例外保証を提供するメソッドでの「ロールバック」コードの実装のためにも呼び出すことができるということです。

14
Raedwald