C++ の std::function とは何ですか? なぜ必要なのですか?

昨日、#include Discord サーバーの誰かが次の質問をしました:

以下は、いくつかのタイプミスの修正と拡張を加えた、質問に対する私の回答です。

Invocables は異なるタイプを持つことができます。パラメータと戻り値の型が同じ

C++ では、ラムダ式は operator() を持つクラスの構文糖衣と見なすことができます 定義されています。例:

int x = 3;
auto lambda = [x](int y) { return x + y; };

とほぼ同等です

struct __Lambda {
  int x;

  int operator()(int y) const {
    return x + y;
  }
};

int x = 3;
auto lambda = __Lambda { .x = x };

1 つの結果は、すべてのラムダ式が異なる型を持つことです。たとえば、以下のスニペットでは、

int x, z;

auto lambda = [x](int y) { return x + y; };
auto lambda2 = [x, z](int y) { return x + y + z; };

lambda そして lambda2 どちらも int を取りますが、異なるタイプがあります int を返します .

C++ にも関数があり、operator() のクラスとは異なります。 .

std::function の動機

では、int を取り、その型を無視して int を返すような呼び出し可能なオブジェクトをどのように保存すればよいでしょうか?

std::functionが必要です そのようなタスクを達成するために。例:

struct S {
  std::function<int(int)> func;
};

この方法で呼び出し可能オブジェクトを格納する標準的なユース ケースはタスク システムです。おそらく、後で実行するためにコールバックをコンテナに格納する必要があります。


struct TaskQueue {
  std::queue<std::function<void()>> queue;
  std::mutex mutex;
  std::condition_variable ready;

  // member functions
  ...
};

タイプ消去

func にする lambda の両方を受け入れます および lambda2 ,std::function 署名を満たす任意の関数オブジェクトまたは単純な関数を受け取るコンストラクターが必要です。また、型消去 を実行する必要があります。 この動作を実現します。

C++ で型消去を実装するにはさまざまな手法があり、この記事に収まるトピックではありませんが、大まかな考え方は std::function 呼び出し可能な関数ポインターと、ラムダ キャプチャ (または関数オブジェクトのデータ メンバー) を格納するためのストレージ スペースが必要です。ラムダ式 (または呼び出し可能なクラス) は任意のサイズのキャプチャを持つことができるため、データはヒープに割り当てる必要があります。ただし、すべての主要な std::function 実装は、小さなバッファの最適化も実行します ラムダが事前定義された容量に収まるほど小さい場合。その場合、すべてのデータを std::function 内に直接割り当てることができます。 オブジェクト自体であり、追加のヒープ割り当ては実行されません。