noexcept
キーワードは多くの関数シグネチャに適切に適用することができますが、実際にいつそれを使用するのか検討すべきかどうかはわかりません。これまで読んだことに基づいて、noexcept
を最後に追加することで、移動コンストラクターがスローしたときに発生するいくつかの重要な問題に対処するようです。しかし、そもそもnoexcept
についてもっと読むように導いたいくつかの実用的な質問に対して満足のいく答えを提供することはまだできません。
私が決して投げないであろうということを知っているが、コンパイラーがそれ自身でそのように決定することができない関数の多くの例があります。関数宣言にnoexcept
を追加する必要がありますそのような場合は?
every関数の宣言の後にnoexcept
を追加する必要があるかどうかを考えなければならないと、プログラマの生産性が大幅に低下します(率直に言って、お尻の痛みです)。どの状況でnoexcept
の使用にもっと注意を払う必要がありますか。また、どの状況で暗黙のnoexcept(false)
を回避することができますか?
noexcept
を使用した後に実際にパフォーマンスの向上が見られるのはいつ頃なのでしょうか。特に、C++コンパイラがnoexcept
を追加した後で、より良いマシンコードを生成できるコードの例を示します。
個人的には、特定の種類の最適化を安全に適用するためにコンパイラに提供される自由度が増したため、noexcept
を気にしています。最近のコンパイラはこのようにnoexcept
を利用しますか?そうでなかったら、私はそれらの何人かが近い将来にそうすると期待できるか。
実際に使用するのに十分な時間がなかったので、これについて「ベストプラクティス」の答えを出すのは時期尚早だと思います。これらが出てきた直後にスロー指定子について尋ねられたならば、答えは今とは非常に異なるでしょう。
すべての関数宣言の後に
noexcept
name__を追加する必要があるかどうかを考えなければならないと、プログラマの生産性が大幅に低下します(そして率直に言って、面倒です)。
関数がスローされないことが明らかな場合は、それを使用してください。
noexcept
name__を使用した後に実際にパフォーマンスの向上が見られると期待できるのはいつですか? [...]個人的に、私はnoexcept
name__を気にかけています。なぜなら、ある種の最適化を安全に適用するためにコンパイラに提供される自由度が増したからです。
noexcept
name__をチェックしてオーバーロードする可能性があるため、最適化の最大の利点はユーザーによる最適化によるもので、コンパイラによるものではないようです。ほとんどのコンパイラは、投げ捨てない例外処理メソッドを採用しているので、コードのマシンコードレベルで大幅に(またはなんでも)変更されることはないでしょう。コード。
ビッグ4(noexcept
name__はすでにnoexcept
name__なので、デストラクタではなく、デストラクタではなく)でnoexcept
name__を使用すると、stdコンテナなどのテンプレートコードでは「よくある」ため、おそらく最良の改善が得られます。例えば、std::vector
は、noexcept
name__とマークされていない限り、クラスの移動を使用しません(または、コンパイラーはそれ以外の場合はそれを推定できます)。
私が最近繰り返し続けているように、セマンティクスが最初。
noexcept
、noexcept(true)
、およびnoexcept(false)
を追加することは、まず第一にセマンティクスについてです。それは偶然にもいくつかの可能な最適化を条件付けるだけです。
プログラマがコードを読むとき、noexcept
の存在はconst
の存在と類似しています。それは、起こり得ることと起こらないことをよりよく理解するのに役立ちます。したがって、関数がスローされるかどうかを知っているかどうかについて考えるのに時間を費やす価値があります。ちなみに、どんな種類の動的メモリ割り当てでもスローされる可能性があります。
それでは、最適化について考えてみましょう。
最も明白な最適化は実際にはライブラリで実行されます。 C++ 11は、関数がnoexcept
であるかどうかを知ることを可能にする多くの特性を提供し、可能であれば、標準ライブラリの実装自体が、それらが操作するユーザー定義オブジェクトに対するnoexcept
操作を優先するためにそれらの特性を使用します。 のような意味を動かします。
コンパイラーは(おそらく)例外処理データからほんの少しの脂肪を削るだけかもしれません、なぜならあなたが嘘をついたかもしれないという事実を考慮に入れるためにはを持っているからです。 noexcept
とマークされた関数がスローされると、std::terminate
が呼び出されます。
これらの意味論は2つの理由で選ばれました:
noexcept
から即座に利益を得る(後方互換性)noexcept
の指定を許可するこれは、実際にはコンパイラのオプティマイザに(潜在的に)大きな違いをもたらします。コンパイラは実際には何年もの間、関数定義の後の空の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
}
bar
にnoexcept
というラベルが付いている場合、この関数のフローグラフは異なります(bar
の末尾とcatchステートメントの間で実行する方法はありません)。 noexcept
とラベル付けされている場合、コンパイラは、baz関数の実行中にxの値が5であることを確認します - x = 5ブロックは、Edgeのないbaz(x)ブロックをbar()
からcatchステートメントまで「支配する」と言われます。それはそれからより効率的なコードを生成するために「定数伝播」と呼ばれる何かをすることができます。ここでbazがインライン化されていれば、xを使ったステートメントは定数を含むかもしれず、実行時評価であったものはコンパイル時評価などに変えることができます。
とにかく、短い答え:noexcept
は、コンパイラがより厳密なフローグラフを生成することを可能にし、フローグラフは、あらゆる種類の一般的なコンパイラ最適化について推論するために使用されます。コンパイラにとって、この種のユーザアノテーションは素晴らしいです。コンパイラはこのことを理解しようとしますが、通常はできません(問題の関数がコンパイラから見えない別のオブジェクトファイルにあるか、見えない関数を他動詞として使用している可能性があります)。あなたがそれをnoexcept
として暗示的にラベル付けすることができないようにあなたが気付いていないことさえ投げられるかもしれない些細な例外(例えば、メモリを割り当てることはbad_allocを投げるかもしれません)。
noexcept
は、一部の操作のパフォーマンスを劇的に向上させることができます。これはコンパイラによって機械語を生成するレベルでは起こりませんが、最も効果的なアルゴリズムを選択することによって起こります。他の人が述べたように、あなたは関数std::move_if_noexcept
を使ってこの選択をします。例えば、std::vector
の増加(例えばreserve
と呼ぶとき)は、強力な例外安全性保証を提供しなければなりません。 T
のmoveコンストラクタがスローしないことを知っていれば、すべての要素を移動するだけです。それ以外の場合は、すべてのT
をコピーする必要があります。これは この記事 で詳しく説明されています。
noexcept
name__を使用した後にパフォーマンスの向上を観察する以外に、現実的にできるのはいつですか?特に、C++コンパイラがnoexceptを追加した後でより良いマシンコードを生成できるコードの例を挙げてください。
うーん、絶対?決して時間ではありませんか?しないでください。
noexcept
name__は、コンパイラーのパフォーマンス最適化用で、const
name__はコンパイラーのパフォーマンス最適化用です。つまり、ほとんどありません。
noexcept
name__は主に、関数が例外をスローできるかどうかをコンパイル時に "you"が検出できるようにするために使用されます。覚えておいてください:ほとんどのコンパイラは、実際に何かを投げない限り、例外に対して特別なコードを発行しません。そのため、noexcept
name__は、あなたに関数の使用方法に関するヒントを与えるだけではなく、コンパイラに関数の最適化方法に関するヒントを与えるのではありません。
move_if_noexcept
のようなテンプレートは、移動コンストラクタがnoexcept
name__で定義されているかどうかを検出し、定義されていない場合は型のconst&
ではなく&&
を返します。安全であれば移動するという言い方です。
一般的に、noexcept
name__は、実際には便利になると思われる場合に使用する必要があります。 is_nothrow_constructible
がその型に当てはまる場合、いくつかのコードは異なるパスをたどります。それを実行するコードを使用している場合は、適切なコンストラクタnoexcept
name__を使用してください。
手短に言うと、moveコンストラクタや同様のコンストラクトに使用してください。ただし、それに慣れる必要はありません。
- 私が決して投げないであろうということを知っているが、コンパイラーがそれ自身でそのように決定することができない関数の多くの例があります。そのような場合はすべて、関数宣言にnoexceptを追加する必要がありますか?
noexcept
は関数インタフェースの一部なので、扱いにくいです。特にあなたがライブラリを書いているなら、あなたのクライアントコードはnoexcept
プロパティに依存することができます。既存のコードを壊す可能性があるため、後で変更するのは難しい場合があります。アプリケーションでのみ使用されるコードを実装している場合は、それほど問題にならないかもしれません。
スローできない関数がある場合は、noexcept
をそのまま使用するのか、それとも将来の実装を制限するのかを検討してください。たとえば、(ユニットテストなどで)例外をスローして不正な引数のエラーチェックを導入したい場合や、例外仕様を変更する可能性のある他のライブラリコードに依存している場合があります。その場合、保守的でnoexcept
を省略した方が安全です。
一方、関数が決して投げてはならないという確信があり、それが仕様の一部であることが正しい場合は、noexcept
と宣言する必要があります。ただし、実装が変更された場合、コンパイラはnoexcept
の違反を検出できないことに注意してください。
- どの状況でnoexceptの使用にもっと注意を払うべきですか。また、どの状況で暗黙のnoexcept(false)を回避できますか?
最も大きな影響を与える可能性が高いので、集中すべき4つのクラスの関数があります。
noexcept(true)
にしない限り、これらは暗黙的にnoexcept(false)
です)これらの関数は一般的にnoexcept
であるべきです、そしてそれはライブラリ実装がnoexcept
プロパティを利用することができる可能性が最も高いです。たとえば、std::vector
は、強力な例外保証を犠牲にすることなく、スローされない移動操作を使用できます。それ以外の場合は、要素のコピーにフォールバックする必要があります(C++ 98の場合と同様)。
この種の最適化はアルゴリズムレベルであり、コンパイラの最適化には依存しません。特に要素のコピーが高価な場合は、大きな影響を与える可能性があります。
- Noexceptを使用した後に実際にパフォーマンスの向上が見られると期待できるのはいつですか?特に、C++コンパイラがnoexceptを追加した後でより良いマシンコードを生成できるコードの例を挙げてください。
例外を指定しないことやthrow()
に対してnoexcept
を使用する利点は、スタックの巻き戻しに関して、標準によってコンパイラの自由度が増すことです。 throw()
の場合でも、コンパイラはスタックを完全にアンワインドしなければなりません(そしてそれはオブジェクト構造の逆順で行わなければなりません)。
一方、noexcept
の場合、これを行う必要はありません。スタックを巻き戻す必要がないという要件はありません(ただし、コンパイラーはそれを許可されています)。この自由度は、スタックをいつでも解くことができるというオーバーヘッドを減らすので、さらなるコード最適化を可能にします。
noexcept、stack unwinding、performance に関する関連質問は、スタックアンワインドが必要な場合のオーバーヘッドについての詳細な説明に入ります。
また、Scott Meyersの著書「Effective Modern C++」、「Item 14:例外が発生しない場合を除いて関数を宣言しない」もお勧めです。
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)を呼び出すことによって無条件に終了します。関数呼び出しからデストラクタを呼び出すことはありません。throw
とnoexcept
の間のスコープからのデストラクタが(例えばcompute()のsに対して)呼び出されるかどうかは実装定義です。プログラムはもうすぐ終了しようとしているので、とにかくどのオブジェクトにも依存してはいけません。noexcept
指定子を追加することで、コードがthrowに対処するように書かれていないことを示します。
私が決して投げないであろうということを知っているが、コンパイラーがそれ自身でそのように決定することができない関数の多くの例があります。そのような場合はすべて、関数宣言にnoexceptを追加する必要がありますか?
"私は彼らが決して投げないことを知っている"と言うとき、あなたはあなたがその関数が投げないことを知っている関数の実装を調べることによって意味します。アプローチは裏返しだと思います。
関数が、関数のdesignの一部であるために例外をスローする可能性があるかどうか、および引数リストと同じくらい重要であり、メソッドがミューテーター(... const
)であるかどうかを検討することをお勧めします。 「この関数は決して例外をスローしない」と宣言することは実装上の制約です。省略しても、関数が例外をスローすることはありません。つまり、現在のバージョンの関数and以降のすべてのバージョンでは例外が発生する可能性があります。実装を難しくするのは制約です。しかし、いくつかのメソッドは実用的であるために制約を持たなければなりません。最も重要なことは、デストラクタから呼び出すことができるだけでなく、強力な例外保証を提供するメソッドでの「ロールバック」コードの実装のためにも呼び出すことができるということです。