618

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

  1. 私が知っている関数の例はたくさんありますが、コンパイラーはそれを自分で判断することはできません。そのような場合はすべてnoexcept、関数宣言に追加する必要がありますか?

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

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

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

4

9 に答える 9

213

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

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

それでは、関数がスローされないことが明らかな場合に使用します。

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

noexcept最適化の最大のメリットは、チェックやオーバーロードの可能性があるため、コンパイラの最適化ではなく、ユーザーの最適化によるもののようです。ほとんどのコンパイラは、スローしない場合はペナルティのない例外処理方法に従うため、コードのマシンコードレベルで大幅に(または何か)変更されるとは思えませんが、おそらく、処理コード。

noexceptビッグ4(コンストラクター、代入、既にあるデストラクタではない)で使用すると、コンテナーなどのテンプレートコードでチェックが「一般的」でnoexceptあるため、最良の改善が得られる可能性があります。たとえば、マークが付けられていない限り、クラスの移動を使用しません(または、コンパイラが別の方法でそれを推測できます)。noexceptstdstd::vectornoexcept

于 2012-05-28T17:02:16.210 に答える
159

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

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

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


さて、次に可能な最適化について説明します。

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

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

これらのセマンティクスは、次の2つの理由で選択されました。

  • 依存関係がまだそれを使用していない場合でもすぐに恩恵を受けるnoexcept(下位互換性)
  • noexcept理論的にはスローする可能性があるが、指定された引数に対しては期待されない関数を呼び出すときの指定を許可する
于 2012-05-28T17:34:43.683 に答える
97

これは実際には、コンパイラーのオプティマイザーに(潜在的に)大きな違いをもたらします。コンパイラーは、実際には、関数定義の後の空のthrow()ステートメント、および適切な拡張機能を介して、この機能を何年も使用してきました。最新のコンパイラーはこの知識を利用して、より優れたコードを生成します。

コンパイラーのほとんどすべての最適化は、関数の「フローグラフ」と呼ばれるものを使用して、何が合法であるかを推論します。フローグラフは、関数の一般に「ブロック」と呼ばれるもの(単一の入口と単一の出口を持つコードの領域)と、フローがジャンプできる場所を示すブロック間のエッジで構成されます。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がラベル付けされている場合、この関数のフローグラフは異なります(実行がendステートメントとcatchステートメントnoexceptの間でジャンプする方法はありません)。barとラベル付けされているnoexcept場合、コンパイラーは、baz関数中にxの値が5であることを確認します。x= 5ブロックはbar()、catchステートメントからのエッジなしでbaz(x)ブロックを「支配」すると言われます。

次に、「定数伝播」と呼ばれる処理を実行して、より効率的なコードを生成できます。ここで、bazがインライン化されている場合、xを使用するステートメントにも定数が含まれている可能性があり、実行時評価であったものをコンパイル時評価などに変換できます。

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

于 2012-05-28T18:33:41.060 に答える
65

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

于 2012-09-24T07:31:45.237 に答える
39

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

ええと、決して?時間はありませんか?一度もない。

noexceptコンパイラのパフォーマンスの最適化と同じ方法で、constコンパイラのパフォーマンスの最適化を行います。つまり、ほとんどありません。

noexcept主に、関数が例外をスローできるかどうかをコンパイル時に「あなた」が検出できるようにするために使用されます。注意:ほとんどのコンパイラは、実際に何かをスローしない限り、例外に対して特別なコードを発行しません。したがって、関数の使用方法に関するヒントを提供するnoexceptのと同じように、関数を最適化する方法に関するヒントをコンパイラーに提供することは問題ではありません。

のようなテンプレートmove_if_noexceptは、moveコンストラクターがで定義されているかどうかを検出し、定義されていない場合は、タイプのの代わりにnoexceptを返します。移動するのが非常に安全である場合、それは移動するという言い方です。const&&&

一般的に、実際にそうすることが有用noexceptであると思うときに使用する必要があります。そのタイプに当てはまる場合、一部のコードは異なるパスを取ります。それを行うコードを使用している場合は、適切なコンストラクターを使用してください。is_nothrow_constructiblenoexcept

つまり、moveコンストラクターや同様のコンストラクトに使用しますが、それに慣れなければならないような気はしません。

于 2012-05-28T16:59:26.607 に答える
32

Bjarneの言葉で(C ++プログラミング言語、第4版、366ページ):

終了が許容可能な応答である場合、キャッチされない例外は、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個のdoubleのメモリを取得できず、。をスローする場合がありstd::bad_allocます。その場合、プログラムは終了します。呼び出すことで無条件に終了しますstd::terminate()(§30.4.1.3)。関数の呼び出しからデストラクタを呼び出すことはありません。throwとの間のスコープからのデストラクタ noexcept(たとえば、compute()のsの場合)が呼び出されるかどうかは、実装によって定義されます。プログラムは間もなく終了するので、とにかくオブジェクトに依存するべきではありません。指定子を追加することnoexceptで、スローに対処するようにコードが記述されていないことを示します。

于 2017-01-09T14:49:12.210 に答える
28
  1. 私が知っている関数の例はたくさんありますが、コンパイラーはそれを自分で判断することはできません。そのような場合はすべて、関数宣言にnoexceptを追加する必要がありますか?

noexcept関数インターフェースの一部であるため、注意が必要です。特に、ライブラリを作成している場合、クライアントコードはプロパティに依存する可能性がありnoexceptます。既存のコードを壊す可能性があるため、後で変更するのは難しい場合があります。アプリケーションでのみ使用されるコードを実装している場合は、それほど心配する必要はありません。

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

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

  1. どのような状況でnoexceptの使用にもっと注意する必要があり、どのような状況で暗黙のnoexcept(false)を回避できますか?

最も大きな影響を与える可能性があるため、集中する必要がある関数には4つのクラスがあります。

  1. ムーブ演算(ムーブ代入演算子とムーブコンストラクター)
  2. スワップ操作
  3. メモリデロケータ(演算子delete、演算子delete [])
  4. デストラクタ(ただし、作成しない限り、これらは暗黙的noexcept(true)に行われますnoexcept(false)

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

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

  1. noexceptを使用した後、いつ現実的にパフォーマンスの向上が見込めますか?特に、noexceptを追加した後、C++コンパイラがより優れたマシンコードを生成できるコードの例を挙げてください。

noexcept例外仕様がないことに対する利点はthrow()、スタックの巻き戻しに関して、標準によってコンパイラーがより自由にできることです。このthrow()場合でも、コンパイラはスタックを完全に巻き戻す必要があります(そして、オブジェクトの構築とはまったく逆の順序でそれを行う必要があります)。

一方、そのnoexcept場合は、その必要はありません。スタックを巻き戻す必要はありません(ただし、コンパイラーはそれを行うことができます)。この自由により、スタックを常に巻き戻すことができるというオーバーヘッドが軽減されるため、コードをさらに最適化できます。

noexcept、スタックの巻き戻し、およびパフォーマンスに関する関連する質問では、スタックの巻き戻しが必要な場合のオーバーヘッドについて詳しく説明します。

また、ScottMeyersの本「EffectiveModernC ++」、「Item 14:例外を発行しない場合を除いて、関数を宣言する」をさらに読むことをお勧めします。

于 2014-12-25T17:08:13.527 に答える
20

私が知っている関数の例はたくさんありますが、コンパイラーはそれを自分で判断することはできません。そのような場合はすべて、関数宣言にnoexceptを追加する必要がありますか?

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

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

于 2012-05-29T13:10:39.940 に答える
1

これは、それが本当に重要になる可能性がある場合を説明するための簡単な例です。

#include <iostream>
#include <vector>
using namespace std;
class A{
 public:
  A(int){cout << "A(int)" << endl;}
  A(const A&){cout << "A(const A&)" << endl;}
  A(const A&&) noexcept {cout << "A(const A&&)" << endl;}
  ~A(){cout << "~S()" << endl;}
};
int main() {
  vector<A> a;
  cout << a.capacity() << endl;
  a.emplace_back(1);
  cout << a.capacity() << endl;
  a.emplace_back(2);
  cout << a.capacity() << endl;
  return 0;
}

これが出力です

0
A(int)
1
A(int)
A(const A&&)
~S()
2
~S()
~S()

移動コンストラクターでnoexceptを削除すると、次のようになります。

0
A(int)
1
A(int)
A(const A&)
~S()
2
~S()
~S()

主な違いはA(const A&&)vsA(const A&&)です。2番目のケースでは、コピーコンストラクターを使用してすべての値をコピーする必要があります。非常に非効率的です!!

于 2021-04-17T03:00:08.287 に答える