15

ラムダ関数変数を使用するには、次の2つの方法があります。

std::function<int(int, int)> x1 = [=](int a, int b) -> int{return a + b;};

//usage
void set(std::function<int(int, int)> x);
std::function<int(int, int)> get();

と:

std::function<int(int, int)>* x2 = new std::function<int(int, int)>([=](int a, int b) -> int{return a + b;});

//usage
void set(std::function<int(int, int)>* x);
std::function<int(int, int)>* get();

ラムダ関数のデータがどのように格納されているのかわからないので、違いは何ですか。

パフォーマンス、メモリ使用量、およびラムダ関数を引数として渡すか、ラムダ関数を返すための最良の方法を知りたいです。
ラムダ関数オブジェクトのサイズが4より大きい場合、またはエラーを回避するために、ポインターを使用することをお勧めします(アトリビューションを行うときにある種のコピーコンストラクターが実行される場合、または不要なときにある種のデストラクタが実行される場合) 。

ラムダ関数変数をどのように宣言する必要がありますか?

編集

コピーや移動は避けたい、同じ機能を使い続けたい。

この例をどのように変更すればよいですか?

int call1(std::function<int(int, int)> f){
    return f(1, 2);
}
int call2(std::function<int(int, int)> f){
    return f(4, 3);
}
std::function<int(int, int)>& recv(int s){
    return [=](int a, int b) -> int{return a*b + s;};
}

int main(){
    std::function<int(int, int)> f1, f2;

    f1 = [=](int a, int b) -> int{return a + b;};
    f2 = recv(10);

    call1(f1);
    call2(f1);
    call1(f2);
    call2(f2);
}

関数recvで参照を返すことができません:

 warning: returning reference to temporary

これは良い解決策ですか?

int call1(std::function<int(int, int)>* f){
    return (*f)(1, 2);
}
int call2(std::function<int(int, int)>* f){
    return (*f)(4, 3);
}

std::function<int(int, int)>* recv(int s){
    return new std::function<int(int, int)>([=](int a, int b) -> int{return a*b + s;});
}

int main(){
    std::function<int(int, int)> f1 = [=](int a, int b) -> int{return a + b;};
    std::function<int(int, int)> *f2 = recv(10);

    call1(&f1);
    call2(&f1);
    call1(f2);
    call2(f2);

    delete f2;
}

編集(結論)

ラムダ関数オブジェクトは、クラスのインスタンスである他のオブジェクトと同じです。割り当て、引数、帰属のルールは同じです。

4

3 に答える 3

22

のポインタを作成することは意味がありませんstd::function。それはあなたに何の利益ももたらさないだけです。メリットの代わりに、メモリを削除する必要があるため、より多くの負担がかかります。

したがって、最初のバージョン(つまり、非ポインターバージョン)がすべてです。

一般に、経験則のように、できるだけ避けてください。 new

また、std::functionラムダを使用するたびに必要になるわけではありません。多くの場合、次のように使用できますauto

auto add = [](int a, int b) { return a + b;};

std::cout << add(100,100) << std::endl;
std::cout << add(120,140) << std::endl;
于 2012-04-14T16:55:36.993 に答える
15

ポインタを使用することを明確にしました。だから...そうしなさい。

ただし、少なくともスマートポインタを使用してください。を適切に使用することでstd::shared_ptr、多くの問題を防ぐことができます。もちろん、循環参照を避けるようにする必要がありますが、ラムダの場合、それはありそうにないようです。

確かに、それはまだひどい考えです。しかし、コードの実際の実行時間に実際に測定可能なメリットをもたらすのではなく、物事を渡すことについて気分を良くするために行われる完全に無意味な時期尚早の最適化であるという意味でのみひどいです。少なくとも、を使用するとshared_ptr、誤ってメモリを削除したり、例外安全性の問題が発生したりする可能性が低くなります。


スタック割り当てと同じくらい簡単にできるものをヒープに割り当てるという悪いC++の慣習と、スマートポインタを使用せずにこのメモリを追跡するという苦痛を無視します。これは、あなたが何をしているのかを知っていることを前提とし、代わりにあなたが尋ねた「パフォーマンス」と「メモリ使用量」の問題に焦点を合わせます。

これをあなたがやりたいことを支持するものとして受け取らないでください。

std::function通常、オブジェクトの内部ヒープ割り当てを行うことで実装されます。型消去のために必要です。したがって、サイズは少なくとも1つのポインターになります。おそらくもっと。ヒープ割り当てのサイズは、vtableポインター+ラムダクラスのサイズになる可能性があります。そして、そのサイズは、キャプチャするものの量と、それが価値によるものか参照によるものかによって決まります。

ほとんどのC++標準ライブラリオブジェクトと同様に、std::functionコピー可能です。コピーを実行すると、実際にコピーされます。同じポインタを持つ2つのオブジェクトがあるふりコピーは実行されません。したがって、それぞれが内部ヒープオブジェクトの独立したコピーを持ちます。つまり、それをコピーすることは、別のヒープ割り当てを行うことを意味します。これにより、ラムダ自体もコピーされます。つまり、ラムダ内にキャプチャされたすべてのものがコピーされます。

ただし、ほとんどのC ++標準ライブラリオブジェクトと同様に、std::function移動可能です。これは、メモリ割り当てを一切行いません

したがって、これを実行したい場合は、かなり安価です。

std::function<int(int, int)> x1 = [=](int a, int b) -> int{return a + b;};

//usage
void set(std::function<int(int, int)> x);
const std::function<int(int, int)> &get();

set(std::move(x1)); //x1 is now *empty*; you can't use it anymore.

関数setは、必要に応じてそれを独自の内部ストレージに移動できます。ここで;をget返すことに注意してください。const&これは、これらの機能のストレージがどこにも行かないことを条件としています。

どれくらい安くなりmoveますか?これはおそらく、のフィールドだけをコピーすることと、std::function元のフィールドを空白にするか無効にすることと同じです。パフォーマンスが重要なコードを使用していない限り、これは心配する必要はありません。

于 2012-04-14T17:04:42.543 に答える
-1

これらの新しいラムダ式(およびstd::functionとstd::bind)が提供する「関数型C++」で私が従う傾向のあるガイドラインは次のとおりです。それらを無視するか、適切と思われる場合は採用してください。

1)名前を付けたいラムダをstd::functionとして割り当てます。それを関数"X(..)"に渡し、それを二度と見ない場合は、関数呼び出しで宣言して、"X"が一時的なものであるという事実を利用できるようにします。

// I want a real name for this (plan to use multiple times, etc.)
// Rarely are you going to do this without moving the lambda around, so
// declaring it as 'auto' is pointless because C++ APIs expect a real type.

std::function<double(double)> computeAbs = [](double d) -> double { return fabs(d); };

// Here, we only ever use the lambda for the call to "X".
// So, just declare it inline. That will enforce our intended usage and probably
// produce more efficient code too.

X([](double r) -> double { return fabs(r); });

2)関数をあちこちに移動する場合は、std::functionの周りにある種の参照カウントラッパーを使用します。アイデアは、実際のstd :: functionへの安全なポインタを渡すことです。これにより、コストのかかるコピーを回避し、newとdeleteについて心配する必要がなくなります。

これを行うための独自のクラスを構築するのは面倒な場合がありますが、ラムダや関数ptrなどを実際にstd::functionに変換するためのヒットを1回だけ受けることができます。次に、「GetFunction」のようなAPIを提供して、それらを期待するAPIの値で運ぶstd::functionを返すことができます。

3)std :: functionまたはlambdasを使用するパブリックAPIの場合、値を渡すだけです。

はい、参照またはr値(これらの「&&」)参照を渡すことで機能することができ、状況によっては適切なソリューションでもあります。しかし、ラムダは、実際の型が文字通りあなたにわからないという点で厄介です。ただし、すべてのプリミティブ型が'int'に変換できるのと同じ方法で、すべてstd :: functionに変換できるため、関数に渡すには、charsとfloatを "f( int a)」。

つまり、次のようにファンクター引数のTYPEにテンプレート化された関数「f」を作成します。

template<typename Functor>
double f(Functor absFunc);

または、全員をstd :: functionに正規化します:

double f(std::function<double(double)> absFunc);

これで、「f([](double r)-> double {return fabs(r);})」を呼び出すと、コンパイラはそれを処理する方法を認識します。ユーザーは、APIをコーディングしなくても、「正しく機能する」ものを手に入れることができます。これは、あなたが望むものです。

于 2014-02-06T07:32:04.083 に答える