web-dev-qa-db-ja.com

グローバルラムダを使用しない理由はありますか?

それ自体の内部にある非キャプチャラムダを使用する関数がありました。例:

_void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}
_

ラムダによって実装された機能が他の場所で必要になったので、ラムダをfoo()からグローバル/名前空間スコープに持ち上げます。私はそれをラムダのままにしておくか、コピーして貼り付けるオプションにするか、適切な関数に変更することができます:

_auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}
_

それを適切な関数に変更するのは簡単ですが、ラムダのままにしておくには何らかの理由notがあるのではないかと思いました。 「通常の」グローバル関数の代わりにラムダをどこでも使用しない理由はありますか?

89
Baruch

グローバルラムダを使用しない非常に重要な理由が1つあります。それは正常ではないためです。

C++の通常の関数構文は、Cの時代から存在しています。プログラマーは何十年もの間、構文の意味とその機能を知っていました(ただし、関数からポインターへの減衰は、経験豊富なプログラマーであっても時々噛み付くことがあります)。 「まったくの初心者」を超えるスキルレベルのC++プログラマが関数の定義を見ると、彼らは何を手に入れているのかを知っています。

グローバルラムダは、まったく別の獣です。通常の関数とは動作が異なります。ラムダはオブジェクトですが、関数はそうではありません。それらにはタイプがありますが、そのタイプは機能のタイプとは異なります。などなど。

これで、他のプログラマーとのコミュニケーションの水準が上がりました。 C++プログラマーは、この関数が何をしているかを理解しようとする場合、ラムダを理解する必要があります。そして、はい、これは2019年なので、まともなC++プログラマーはラムダがどのように見えるかを理解している必要があります。しかし、それはまだより高いバーです。

そして、たとえ彼らがそれを理解したとしても、そのプログラマーの心の疑問は...なぜこのコードの作成者はそれをそのように書いたのでしょうか?また、その質問に対する適切な回答がない場合(たとえば、範囲のカスタマイズポイントのように明示的にwantでオーバーロードを禁止するため)、共通のメカニズムを使用する必要があります。

必要に応じて、予想されるソリューションを新規のソリューションよりも優先します。最も簡単な方法でポイントを伝えます。

61
Nicol Bolas

グローバルラムダを通常の関数のドロップイン置換として回避するいくつかの理由を考えることができます。

  • 通常の関数はオーバーロードできます。ラムダはできません(ただし、これをシミュレートする手法はあります)
  • それらは関数のようであるという事実にもかかわらず、このような非キャプチャラムダでもメモリを占有します(通常、非キャプチャ用に1バイト)。
    • コメントで指摘されているように、最近のコンパイラはas-ifルールの下でこのストレージを最適化します

「ステートフルファンクタ(クラス)を置き換えるためにラムダを使用すべきではないのはなぜですか?」

  • クラスはラムダよりも制限が少ないため、最初に到達する必要があります
    • (パブリック/プライベートデータ、オーバーロード、ヘルパーメソッドなど)
  • ラムダに状態がある場合、いつそれがグローバルになるかについて推論するのはさらに困難です。
    • 可能な限り狭いスコープでクラスのinstanceを作成することをお勧めします
  • 非キャプチャラムダを関数ポインターに変換することは既に困難であり、そのキャプチャで何かを指定するラムダは不可能です。
    • クラスは、関数ポインターを作成する簡単な方法を提供します。また、クラスは多くのプログラマーにとってより快適なものです
  • キャプチャー付きのラムダはデフォルトで作成できません(C++ 20の場合。以前は、デフォルトのコンストラクターはありませんでした)。
53
AndyG

質問した後、これを行わない理由を考えました:これらは変数であるため、静的初期化順序Fiascohttps ://isocpp.org/wiki/faq/ctors#static-init-order )。これにより、バグが発生する可能性があります。

9
Baruch

「通常の」グローバル関数の代わりにラムダをどこでも使用しない理由はありますか?

一定レベルの複雑さの問題には、少なくとも同じ複雑さの解決策が必要です。しかし、同じ問題に対してそれほど複雑ではないソリューションがある場合、より複雑なソリューションを使用する正当な理由はありません。なぜ必要のない複雑さを導入するのですか?

ラムダと関数の間では、関数は単純に2つのエンティティのより単純な種類です。ラムダを使用しないことを正当化する必要はありません。 1つを使用して正当化する必要があります。ラムダ式は、通常のすべての特別なメンバー関数、関数呼び出し演算子、およびこの場合は関数ポインターへの暗黙の変換演算子を持つ名前のないクラス型であるクロージャー型を導入し、その型のオブジェクトを作成します。ラムダ式からのグローバル変数のコピー初期化は、単に関数を定義するだけではなく、たくさんを実行するだけです。 6つの暗黙的に宣言された関数でクラス型を定義し、さらに2つの演算子関数を定義して、オブジェクトを作成します。コンパイラーはもっと多くのことをしなければなりません。ラムダの機能が必要ない場合は、ラムダを使用しないでください…

8
Michael Kenzel

ラムダは 無名関数 です。

名前付きラムダを使用している場合、それは基本的に名前付き無名関数を使用していることを意味します。この矛盾を回避するには、関数を使用することもできます。

6
Eric Duminil

それをラムダのままにしない理由があるなら? 「通常の」グローバル関数の代わりにラムダをどこでも使用しない理由はありますか?

以前はグローバルファンクタの代わりに関数を使用していたため、一貫性と最小驚きの原則が壊れています。

主な違いは次のとおりです。

  • 関数はオーバーロードできますが、ファンクタはオーバーロードできません。
  • 関数はファンクタではなくADLで見つけることができます。
5
Jarod42