web-dev-qa-db-ja.com

std :: bindがメンバー関数と連携する方法

私はstd::bindしかし、それをメンバークラス関数で使用すると、どのように機能するかまだわかりません。

次の機能がある場合:

double my_divide (double x, double y) {return x/y;}

私は次のコード行を完全によく理解しています:

auto fn_half = std::bind (my_divide,_1,2);               // returns x/2

std::cout << fn_half(10) << '\n';                        // 5

しかし、メンバー関数にバインドする次のコードでは、いくつか質問があります。

struct Foo {
    void print_sum(int n1, int n2)
    {
        std::cout << n1+n2 << '\n';
    }
    int data = 10;
};

Foo foo;

auto f = std::bind(&Foo::print_sum, &foo, 95, _1);
f(5);
  • なぜ最初の引数が参照なのですか?理論的な説明をしたいと思います。

  • 2番目の引数はオブジェクトへの参照であり、理解するのが最も複雑な部分です。 std::bindコンテキストが必要です、私は正しいですか?いつもこんな感じ? std::bind最初の引数がメンバー関数であるときに参照を必要とする何らかの実装?

19
Tarod

「最初の引数は参照です」と言うとき、あなたは確かに「最初の引数はpointer "である」と言うつもりでした:_&_演算子はオブジェクトのアドレスを取り、ポインターを生成します。

この質問に答える前に、少し戻って、使用するときのstd::bind()の最初の使用を見てみましょう。

_std::bind(my_divide, 2, 2)
_

機能を提供します。関数がどこかに渡されると、関数はポインターになります。上記の式はこれと同等で、明示的にアドレスを取得します

_std::bind(&my_divide, 2, 2)
_

std::bind()の最初の引数は、関数の呼び出し方法を識別するオブジェクトです。上記の場合、それはdouble(*)(double, double)型の関数へのポインターです。適切な関数呼び出し演算子を持つ他の呼び出し可能なオブジェクトも同様です。

メンバー関数は非常に一般的であるため、std::bind()はメンバー関数へのポインターの処理をサポートします。 _&print_sum_を使用すると、メンバー関数、つまりvoid (Foo::*)(int, int)型のエンティティへのポインターを取得するだけです。関数名は暗黙的に関数へのポインターに減衰します。つまり、_&_は省略できますが、メンバー関数(またはデータメンバー)については同じではありません。メンバー関数へのポインターを取得するには_&_を使用するために必要です。

メンバーへのポインターはclassに固有ですが、クラスのどのオブジェクトでも使用できることに注意してください。つまり、特定のオブジェクトに依存しません。 C++には、オブジェクトに直接バインドされたメンバー関数を取得する直接的な方法がありません(C#では、メンバー名が適用されたオブジェクトを使用してオブジェクトに直接バインドされた関数を取得できると思いますが、それは10年以上です最後にC#を少しプログラミングしました)。

内部的に、std::bind()は、メンバー関数へのポインターが渡されたことを検出し、最初の引数でstd::mem_fn()を使用するなどして、呼び出し可能なオブジェクトに変える可能性が高いです。 static以外のメンバー関数にはオブジェクトが必要なので、解決呼び出し可能オブジェクトへの最初の引数は、適切なクラスのオブジェクトへの参照または[スマート]ポインターです。

メンバー関数へのポインターを使用するには、オブジェクトが必要です。 std::bind()でメンバーへのポインターを使用する場合、std::bind()の2番目の引数は、オブジェクトの送信元を指定する必要があります。あなたの例では

_std::bind(&Foo::print_sum, &foo, 95, _1)
_

結果の呼び出し可能オブジェクトは_&foo_、つまりfoo(タイプ_Foo*_)へのポインターをオブジェクトとして使用します。 std::bind()は、ポインタのように見えるもの、適切な型の参照に変換可能なもの(_std::reference_wrapper<Foo>_など)、またはオブジェクトの[コピー]をオブジェクトとして使用するのに十分スマートです。最初の引数は、メンバーへのポインターです。

メンバーへのポインタを見たことがないのではないかと思われます。以下に簡単な例を示します。

_#include <iostream>

struct Foo {
    int value;
    void f() { std::cout << "f(" << this->value << ")\n"; }
    void g() { std::cout << "g(" << this->value << ")\n"; }
};

void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
    (foo1->*fun)();  // call fun on the object foo1
    (foo2->*fun)();  // call fun on the object foo2
}

int main() {
    Foo foo1{1};
    Foo foo2{2};

    apply(&foo1, &foo2, &Foo::f);
    apply(&foo1, &foo2, &Foo::g);
}
_

関数apply()は、単にFooオブジェクトへの2つのポインターとメンバー関数へのポインターを取得します。各オブジェクトでポイントされているメンバー関数を呼び出します。この面白い_->*_演算子は、メンバーへのポインターをオブジェクトへのポインターに適用しています。オブジェクトへのメンバーへのポインターを適用する_.*_演算子もあります(または、オブジェクトのように動作するため、オブジェクトへの参照)。メンバー関数へのポインターにはオブジェクトが必要なので、オブジェクトを要求するこの演算子を使用する必要があります。内部的に、std::bind()は同じことが起こるように調整します。

apply()が2つのポインターと_&Foo::f_で呼び出された場合、メンバーf()がそれぞれのオブジェクトで呼び出される場合とまったく同じように動作します。同様に、2つのポインターと_&Foo::g_を使用してapply()を呼び出すと、それぞれのオブジェクトでメンバーg()が呼び出されるのとまったく同じように動作します(意味的な動作は同じです)しかし、コンパイラーは関数をインライン展開するのがはるかに困難になる可能性が高く、通常、メンバーへのポインターが関係している場合はそうしません。

34
Dietmar Kühl

std :: bind docs :から

bind( F&& f, Args&&... args );ここで、fはCallableです。これは、メンバー関数へのポインターです。この種類のポインターには、通常の関数へのポインターと比較して、いくつかの特別な構文があります。

_typedef  void (Foo::*FooMemberPtr)(int, int);

// obtain the pointer to a member function
FooMemberPtr a = &Foo::print_sum; //instead of just a = my_divide

// use it
(foo.*a)(1, 2) //instead of a(1, 2)
_

_std::bind_(および_std::invoke_ 一般的に )は、これらすべてのケースを統一的な方法でカバーします。 fFooのメンバーへのポインタである場合、バインドするために提供される最初のArgFoobind(&Foo::print_sum, foo, ...)も機能しますが、fooがコピーされます)またはpointerto Foo、例のように持っていた。

メンバーへのポインター 、および 1 および 2 についてさらに読むと、bindが期待するものと、ストアド関数を呼び出す方法に関する完全な情報が得られます。

代わりに、_std::bind_の代わりにラムダを使用することもできます。

_auto f = [&](int n) { return foo.print_sum(95, n); }
_
6