免責事項:私の答えは現実に比べていくらか単純化されています(詳細は脇に置きます)が、全体像はここにあります. また、標準では、ラムダまたはstd::function
内部で実装する必要がある方法を完全に指定していません (実装にはある程度の自由があります)。そのため、実装の詳細に関する他の議論と同様に、コンパイラはこの方法で正確に実行する場合と実行しない場合があります。
しかし、繰り返しになりますが、これは VTables と非常によく似た主題です。標準ではあまり義務付けられていませんが、賢明なコンパイラはこの方法で行う可能性が非常に高いため、少し掘り下げる価値があると思います。:)
ラムダス
ラムダを実装する最も簡単な方法は、名前のないものstruct
です。
auto lambda = [](Args...) -> Return { /*...*/ };
// roughly equivalent to:
struct {
Return operator ()(Args...) { /*...*/ }
}
lambda; // instance of the unnamed struct
他のクラスと同様に、そのインスタンスを渡すときに、コードをコピーする必要はなく、実際のデータだけをコピーする必要があります (ここでは、まったくコピーしません)。
値によってキャプチャされたオブジェクトは、次のファイルにコピーされますstruct
:
Value v;
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ };
// roughly equivalent to:
struct Temporary { // note: we can't make it an unnamed struct any more since we need
// a constructor, but that's just a syntax quirk
const Value v; // note: capture by value is const by default unless the lambda is mutable
Temporary(Value v_) : v(v_) {}
Return operator ()(Args...) { /*... use v, captured by value...*/ }
}
lambda(v); // instance of the struct
v
繰り返しますが、それを渡すということは、コード自体ではなく、データ ( ) を渡すことを意味するだけです。
同様に、参照によってキャプチャされたオブジェクトは、次のように参照されますstruct
。
Value v;
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ };
// roughly equivalent to:
struct Temporary {
Value& v; // note: capture by reference is non-const
Temporary(Value& v_) : v(v_) {}
Return operator ()(Args...) { /*... use v, captured by reference...*/ }
}
lambda(v); // instance of the struct
ラムダ自体に関しては、これでほとんどすべてです (省略したいくつかの実装の詳細を除いて、それがどのように機能するかを理解することには関係ありません)。
std::function
std::function
は、あらゆる種類のファンクター (ラムダ、スタンドアロン/静的/メンバー関数、私が示したようなファンクター クラスなど) の汎用ラッパーです。
std::function
これらすべてのケースをサポートする必要があるため、の内部はかなり複雑です。ファンクターの正確なタイプに応じて、これには少なくとも次のデータが必要です (実装の詳細を提供または取得します)。
または、
- ファンクターのコピーへのポインター[以下の注を参照] (あなたが正しく指摘したように、任意のタイプのファンクターを許可するために動的に割り当てられます)。
- 呼び出されるメンバー関数へのポインター。
- ファンクターとそれ自体の両方をコピーできるアロケーターへのポインター (任意のタイプのファンクターを使用できるため、ポインターからファンクターへのポインターを使用する必要が
void*
あり、そのようなメカニズムが必要です。おそらく、ポリモーフィズム aka. base を使用します)。クラス + 仮想メソッド、派生クラスはtemplate<class Functor> function(Functor)
コンストラクターでローカルに生成されます)。
どの種類のファンクターを格納する必要があるかを事前に知らないため (これは、std::function
再割り当てできるという事実から明らかです)、考えられるすべてのケースに対処し、実行時に決定を下す必要があります。
注:標準がどこでそれを義務付けているかはわかりませんが、これは間違いなく新しいコピーであり、基礎となるファンクターは共有されていません:
int v = 0;
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; };
std::function<void()> g = f;
f(); // 0
f(); // 1
g(); // 0
g(); // 1
したがって、 a を渡すと、std::function
少なくともこれらの 4 つのポインター (実際、GCC 4.7 では 64 ビットsizeof(std::function<void()>
は 32、つまり 4 つの 64 ビット ポインター) と、オプションでファンクターの動的に割り当てられたコピー (既に述べたように、含まれるもののみ) が含まれます。キャプチャされたオブジェクト、コードをコピーしないでください)。
質問への回答
このような関数にラムダを渡すコストはいくらですか? [質問の文脈:値による]
ご覧のとおり、主にファンクター (手作りのstruct
ファンクターまたはラムダ) とそれに含まれる変数に依存します。ファンクターを直接値渡しする場合と比較した場合のオーバーヘッドstruct
はごくわずかですが、struct
参照渡しの場合よりもはるかに高くなります。
const&
コピーが作成されないように、渡された各関数オブジェクトをマークする必要がありますか?
これは一般的な方法で答えるのが非常に難しいと思います。移動できるように、参照渡しconst
、値渡し、右辺値参照渡しが必要な場合があります。それは本当にコードのセマンティクスに依存します。
どちらを選択するかに関するルールは、IMO とはまったく別のトピックですが、他のオブジェクトと同じであることを覚えておいてください。
とにかく、これで情報に基づいた決定を下すためのすべての鍵が手に入りました (これも、コードとそのセマンティクスによって異なります)。