問題のラムダには実際には状態がありません。
診る:
struct lambda {
auto operator()() const { return 17; }
};
そして、もしあればlambda f;
、それは空のクラスです。上記はlambda
ラムダと機能的に類似しているだけでなく、ラムダが(基本的に)実装されている方法です!(関数ポインター演算子への暗黙のキャストも必要です。名前lambda
は、コンパイラーが生成した疑似GUIDに置き換えられます)
C ++では、オブジェクトはポインターではありません。それらは実際のものです。データを格納するために必要なスペースを使い果たすだけです。オブジェクトへのポインタは、オブジェクトより大きくなる場合があります。
そのラムダを関数へのポインタと考えるかもしれませんが、そうではありません。をauto f = [](){ return 17; };
別の関数またはラムダに再割り当てすることはできません!
auto f = [](){ return 17; };
f = [](){ return -42; };
上記は違法です。呼び出される関数f
を格納する余地はありません。その情報は!の値ではなく、の型で格納されf
ますf
。
これを行った場合:
int(*f)() = [](){ return 17; };
またはこれ:
std::function<int()> f = [](){ return 17; };
ラムダを直接保存しなくなりました。どちらの場合も、f = [](){ return -42; }
は合法です。したがって、これらの場合、の値で呼び出す関数を格納しますf
。そしてsizeof(f)
、もはや1
ではなく、sizeof(int(*)())
それ以上です(基本的に、予想どおりポインタサイズ以上になります。 std::function
標準によって暗示される最小サイズがあります(特定のサイズまで「自分自身の」呼び出し可能オブジェクトを格納できる必要があります)。少なくとも実際には関数ポインタと同じ大きさです)。
このint(*f)()
場合、ラムダを呼び出した場合と同じように動作する関数への関数ポインタを格納します。これは、ステートレスラムダ([]
キャプチャリストが空のラムダ)に対してのみ機能します。
このstd::function<int()> f
場合、std::function<int()>
(この場合は)新しい配置を使用してサイズ1のラムダのコピーを内部バッファーに格納する型消去クラスインスタンスを作成しています(より大きなラムダが渡された場合(より多くの状態) )、ヒープ割り当てを使用します)。
推測として、このようなものはおそらくあなたが考えていることです。ラムダは、そのシグネチャによって型が記述されているオブジェクトであること。C ++では、ラムダを手動の関数オブジェクト実装よりもコストをかけずに抽象化することが決定されました。これにより、ラムダをstd
アルゴリズム(または類似のもの)に渡し、アルゴリズムテンプレートをインスタンス化するときにその内容をコンパイラーに完全に表示することができます。ラムダのタイプがのようなstd::function<void(int)>
場合、その内容は完全に表示されず、手作りの関数オブジェクトの方が速い場合があります。
C ++標準化の目標は、手作りのCコードに対するオーバーヘッドがゼロの高水準プログラミングです。
あなたf
は自分が実際にはステートレスであることを理解したので、頭の中に別の質問があるはずです。ラムダにはステートがありません。どうしてサイズがないの0
?
短い答えがあります。
C ++のすべてのオブジェクトは、標準では最小サイズ1である必要があり、同じタイプの2つのオブジェクトが同じアドレスを持つことはできません。タイプの配列はT
要素がsizeof(T)
離れて配置されるため、これらは接続されます。
現在、状態がないため、場所をとらない場合があります。これが「単独」の場合は発生しませんが、状況によっては発生する可能性があります。 std::tuple
および同様のライブラリコードがこの事実を利用します。以下にその仕組みを示します。
ラムダはoperator()
オーバーロードされたクラスと同等なので、ステートレスラムダ([]
キャプチャリストを含む)はすべて空のクラスです。彼らは持っているsizeof
の1
。実際、それらから継承する場合(許可されています!)、同じタイプのアドレスの衝突が発生しない限り、それらは領域を占有しません。(これは空の基本最適化と呼ばれます)。
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
これsizeof(make_toy( []{std::cout << "hello world!\n"; } ))
はsizeof(int)
(まあ、評価されていないコンテキストではラムダを作成できないため、上記は不正です。namedthen auto toy = make_toy(blah);
do を作成する必要がありますがsizeof(blah)
、これは単なるノイズです)。 sizeof([]{std::cout << "hello world!\n"; })
まだです1
(同様の資格)。
別のタイプのおもちゃを作成する場合:
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
これにはラムダの2つのコピーがあります。彼らは同じアドレスを共有できないので、sizeof(toy2(some_lambda))
です2
!
struct
とoperator()
)