web-dev-qa-db-ja.com

新しいコードでC ++ 17の[[nodiscard]]をほとんどどこでも使用しない理由は何ですか?

C++ 17は[[nodiscard]]属性を導入しています。これにより、プログラマーは、返されたオブジェクトが呼び出し元によって破棄された場合にコンパイラーが警告を生成するように関数をマークできます。同じ属性をクラスタイプ全体に追加できます。

元の proposal でこの機能の動機について読みましたが、C++ 20が std::vector::empty のような標準関数に属性を追加することを知っています、その名前は戻り値に関して明確な意味を伝えません。

それはクールで便利な機能です。実際、tooはほとんど役に立ちそうです。私が[[nodiscard]]について読んだところはどこでも、選択したいくつかの関数または型に追加するだけで、残りのことは忘れるかのように人々が議論しています。しかし、特に新しいコードを記述する場合、破棄できない値が特別なケースになるのはなぜですか?破棄された戻り値は、通常、バグまたは少なくともリソースの無駄ではありませんか?

また、C++自体の設計原則の1つは、コンパイラができるだけ多くのエラーをキャッチする必要があるのではないでしょうか。

その場合は、独自のレガシー以外のコードで[[nodiscard]]をほとんどすべての非void関数とほとんどすべてのクラス型に追加してみませんか?

私は自分のコードでそれを試みました、そしてそれがJavaのように感じ始めるほどひどく冗長であることを除いて、それはうまく働きます。コンパイラにデフォルトで破棄された戻り値について警告することは、はるかに自然に思えますexceptあなたがあなたの意図をマークする他のケース[*]

標準的な提案、ブログエントリ、スタックオーバーフローの質問、またはインターネット上の他の場所で、この可能性についてのディスカッションをまったく見なかったので、何かを見逃しているに違いありません。

なぜそのようなメカニズムが新しいC++コードで意味をなさないのでしょうか?冗長性は、ほとんどすべての場所で[[nodiscard]]を使用しない唯一の理由ですか?


[*]理論的には、代わりに[[maydiscard]]属性のようなものがあり、標準ライブラリの実装でprintfのような関数に遡及的に追加することもできます。

81
Christian Hackl

古い標準との互換性を必要としない新しいコードでは、必要に応じてその属性を使用してください。しかし、C++の場合、_[[nodiscard]]_は不適切なデフォルトになります。あなたは提案します:

意図をマークする他のいくつかの場合を除いて、デフォルトで破棄された戻り値についてコンパイラーに警告させるのははるかに自然なことです。

これにより、既存の正しいコードが突然多くの警告を発するようになります。このような変更は、既存のコードが引き続き正常にコンパイルされるため、技術的には下位互換性があると見なすことができますが、実際にはセマンティクスの大幅な変更になります。

非常に大きなコードベースを持つ既存の成熟した言語の設計決定は、完全に新しい言語の設計決定とは必然的に異なります。これが新しい言語の場合、デフォルトで警告を出すのが賢明です。たとえば、Nim言語では不要な値を明示的に破棄する必要があります。これは、C++ですべての式ステートメントを_(void)(...)_キャストでラップするのと似ています。

_[[nodiscard]]_属性は、次の2つの場合に最も役立ちます。

  • 関数が特定の結果を返す以外に効果がない場合、つまり純粋な場合。結果が使用されない場合、呼び出しは確かに役に立ちません。一方、結果の破棄は正しくありません。

  • 戻り値をチェックする必要がある場合。スローする代わりにエラーコードを返すCのようなインターフェイスの場合。これが主な使用例です。慣用的なC++の場合、これは非常にまれです。

これらの2つのケースは、値を返す不純な関数の巨大なスペースを残します。そのような警告は誤解を招くでしょう。例えば:

  • 要素を削除し、削除された要素のコピーを返す.pop()メソッドを持つキューデータ型について考えてみます。そのような方法はしばしば便利です。ただし、コピーを取得せずに要素のみを削除したい場合があります。これは完全に合法であり、警告は役に立ちません。別のデザイン(_std::vector_など)はこれらの責任を分割しますが、他のトレードオフがあります。

    場合によっては、とにかくコピーを作成する必要があるので、 [〜#〜] rvo [〜#〜] のおかげでコピーを返すのは無料です。

  • さらなる操作を実行できるように、各操作がオブジェクトを返す、流れるようなインターフェースを検討してください。 C++では、最も一般的な例はストリーム挿入演算子_<<_です。 _[[nodiscard]]_オーバーロードのすべてに_<<_属性を追加するのは非常に面倒です。

これらの例は、「デフォルトでnodiscardを使用するC++ 17」言語で警告なしに慣用的なC++コードをコンパイルするのは非常に退屈であることを示しています。

光沢のある新しいC++ 17コード(これらの属性を使用できる場所)は、古いC++標準を対象とするライブラリと一緒にコンパイルされる場合があることに注意してください。この下位互換性は、C/C++エコシステムにとって重要です。したがって、nodiscardをデフォルトにすると、一般的な慣用的な使用例で多くの警告が発生します。この警告は、ライブラリのソースコードを広範囲に変更しないと修正できません。

間違いなく、ここでの問題はセマンティクスの変更ではありませんが、各C++標準の機能は、ファイルごとのスコープではなく、コンパイル単位ごとのスコープに適用されます。将来のC++標準がヘッダーファイルから離れた場合、そのような変更はより現実的です。

81
amon

ほとんどどこでも[[nodiscard]]しない理由:

  1. (メジャー:)これにより、ヘッダーにwayノイズが多すぎます。
  2. (専攻:)他の人のコードについては、投機的に推測する必要があるとは思わない。私が返す戻り値を破棄しますか?よし、ノックアウトする。
  3. (マイナー:) C++ 14との非互換性を保証します

これをデフォルトにした場合、すべてのライブラリ開発者に、すべてのユーザーに戻り値を破棄しないよう強制することになります。それは恐ろしいことです。または、無数の関数に[[maydiscard]]を追加するように強制しますが、これも恐ろしいことです。

11
einpoklum

例として:operator <<の戻り値は、絶対に必要かまったく役に立たない呼び出しに依存しています。 (std :: cout << x << y、1つ目はストリームを返すため必要で、2つ目はまったく役に立ちません)。ここで、printfと比較してください。誰もがエラーチェックのためにある戻り値を破棄しますが、operator <<にはエラーチェックがないため、そもそもそれほど有用ではありませんでした。したがって、どちらの場合でも、nodiscardは単に有害です。

0
gnasher729