C++のfunction,bind,lambda


本稿はC++0 xシリーズの第4編で,主にC++0 xに新たに追加されたlambda式,functionオブジェクト,bindメカニズムを内容とする.この3つを一緒に話すのは、この3つの間に非常に密接な関係があり、比較学習を通じて、この部分の理解を深めるからです.最初の間に,まずlambdaを理解する基礎となる概念,closure(閉パッケージ)について述べる.次にwikipediaのコンピュータ分野のclosureの定義を見てみましょう.
A closure (also lexical closure, function closure or function value) is a function together with
a referencing environment for the non-local variables of that function.

上の大義はclosureが関数とその参照する非ローカル変数のコンテキスト環境の集合であるということです.定義から、closureは定義範囲外の変数、すなわち上述したnon-local vriablesにアクセスでき、その機能が大幅に増加することがわかります.closureに関する最も重要な応用はコールバック関数であり,これもここでfunction,bind,lambdaを一緒に述べる主な原因であり,それら3つがコールバック関数を使用する過程でそれぞれ神通力を示している.次はこの3つの神秘的なベールを一歩一歩つなぎます.
1.function我々は、C++において、呼び出し可能なエンティティが主に関数、関数ポインタ、関数参照を含み、関数によって指定されたオブジェクトに暗黙的に変換したり、opetator()を実現したオブジェクト(すなわち、C++98のfunctor)を含むことを知っている.C++0 xには、std::functionオブジェクト、std::functionオブジェクトが新たに追加されました.functionオブジェクトは、C++に既存の呼び出し可能エンティティのタイプに対して安全な小包です(関数ポインタのような呼び出し可能エンティティは、タイプが安全ではないことを知っています).いくつかのfunctionオブジェクトに関する例を見てみましょう.
#include < functional>
 
std::function< size_t(const char*)> print_func;
 
/// normal function -> std::function object
size_t CPrint(const char*) { ... }
print_func = CPrint;
print_func("hello world"):
 
/// functor -> std::function object
class CxxPrint
{
public:
    size_t operator()(const char*) { ... }
};
CxxPrint p;
print_func = p;
print_func("hello world");
上記の例では、一般的な関数とfunctorをstd::functionオブジェクトに割り当て、そのオブジェクトによって呼び出します.他のC++の呼び出し可能なエンティティは、上記のように使用できます.std::functionの小包により、呼び出し可能なエンティティを通常のオブジェクトと同じように渡すことができ、タイプのセキュリティの問題を解決することができます.std::functionの基本的な使い方を理解しました.次に、使用中の注意点を見てみましょう.
(1)呼び出し可能エンティティのstdへの変換について::functionオブジェクトは、a.変換後のstd::functionオブジェクトのパラメータが呼び出し可能エンティティのパラメータに変換できるb.呼び出し可能エンティティの戻り値がstd::functionオブジェクトの(ここで、呼び出し可能なすべてのエンティティの戻り値は、voidを返すstd::functionオブジェクトの戻り値と互換性があることに注意してください).
(2)std::functionオブジェクトは、(1)の条件を満たす任意の呼び出し可能エンティティをrefer toで満たすことができる.
(3)std::function objectの最大の用途は関数コールバックを実現することであり、使用者は、等しいまたは等しくないを検査するために使用できないことに注意しなければならない.

2.bind bindは、指定された呼び出し可能エンティティのいくつかのパラメータを既存の変数にバインドして、コールバック関数の使用中にも役立つ新しい呼び出し可能エンティティを生成するメカニズムです.C++98には、functorの最初のパラメータと2番目のパラメータをバインドするために使用できる2つの関数bind 1 stとbind 2 ndがあり、いずれも1つのパラメータしかバインドできません.様々な制限により、bind 1 stおよびbind 2 ndの可用性が大幅に低下する.C++0 xではstd::bindが提供され、バインドされたパラメータの個数は制限されず、バインドされた具体的なパラメータも制限されず、ユーザーが指定し、このbindこそ本当の意味でのバインドであり、それがあればbind 1 stとbind 2 ndは何の役にも立たないので、C++0 xではbind 1 stとbind 2 ndの使用は推奨されず、deprecatedである.次に、bindの使用例を見てみましょう.
#include < functional>
 
int Func(int x, int y);
auto bf1 = std::bind(Func, 10, std::placeholders::_1);
bf1(20); ///< same as Func(10, 20)
 
class A
{
public:
    int Func(int x, int y);
};
 
A a;
auto bf2 = std::bind(&A::Func, a, std::placeholders::_1, std::placeholders::_2);
bf2(10, 20); ///< same as a.Func(10, 20)
 
std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, 100);
bf3(10); ///< same as a.Func(10, 100)
の上の例では、bf 1は2つのパラメータの一般関数の最初のパラメータを10にバインドし、新しいパラメータの呼び出し可能なエンティティを生成します.bf 2は、クラスメンバー関数をクラスオブジェクトにバインドし、通常の関数のような新しい呼び出し可能なエンティティを生成する.bf 3はクラスメンバー関数をクラスオブジェクトと2番目のパラメータにバインドし,新しいstd::functionオブジェクトを生成する.上の例を見て、bindを使用する上で注意しなければならないことを説明します.
(1)bind予めバインドされたパラメータは、特定の変数または値を伝達する必要があり、予めバインドされたパラメータはpass-by-valueのである.
(2)事前にバインドされていないパラメータについてはstd::placeholdersを呼び出し,1から順に増加します.placeholderはpass-by-referenceのです
(3)bindの戻り値は呼び出し可能エンティティであり、std::functionオブジェクトに直接割り当てることができる.
(4)バインドされたポインタ、参照タイプのパラメータについて、ユーザは、呼び出し可能なエンティティ呼び出しの前に、これらのパラメータが利用可能であることを保証する必要がある.
(5)クラスのthisは、オブジェクトまたはポインタによってをバインドすることができる.

3.lambdaはfunctionとbindについて話しました.次にlambdaを見てみましょう.pythonの基礎を持っている友达は、lambdaに慣れていないと信じています.ここを見た皆さん、前に述べたclosureの概念をもう一度思い出してください.lambdaはclosureを実現するためのものです.その最大の用途もコールバック関数であり,前述したfunctionとbindと千丝万缕の関系がある.次に、lambdaの廬山の正体を例によって見てみましょう.
vector< int> vec;
/// 1. simple lambda
auto it = std::find_if(vec.begin(), vec.end(), [](int i) { return i > 50; });
class A
{
public:
    bool operator(int i) const { return i > 50; }
};
auto it = std::find_if(vec.begin(), vec.end(), A());
 
/// 2. lambda return syntax
std::function< int(int)> square = [](int i) -> int { return i * i; }
 
/// 3. lambda expr: capture of local variable
{
    int min_val = 10;
    int max_val = 1000;
 
    auto it = std::find_if(vec.begin(), vec.end(), [=](int i) {
        return i > min_val && i < max_val; 
        });
 
    auto it = std::find_if(vec.begin(), vec.end(), [&](int i) {
        return i > min_val && i < max_val;
        });
 
    auto it = std::find_if(vec.begin(), vec.end(), [=, &max_value](int i) {
        return i > min_val && i < max_val;
        });
}
 
/// 4. lambda expr: capture of class member
class A
{
public:
    void DoSomething();
 
private:
    std::vector<int>  m_vec;
    int               m_min_val;
    int               m_max_va;
};
 
/// 4.1 capture member by this
void A::DoSomething()
{
    auto it = std::find_if(m_vec.begin(), m_vec.end(), [this](int i){
        return i > m_min_val && i < m_max_val; });
}
 
/// 4.2 capture member by default pass-by-value
void A::DoSomething()
{
    auto it = std::find_if(m_vec.begin(), m_vec.end(), [=](int i){
        return i > m_min_val && i < m_max_val; });
}
 
/// 4.3 capture member by default pass-by-reference
void A::DoSomething()
{
    auto it = std::find_if(m_vec.begin(), m_vec.end(), [&](int i){
        return i > m_min_val && i < m_max_val; });
}
の上の例はlambda表現の基本的な使い方を基本的にカバーしています.各例を1つずつ分析します(符号は上記のコード注釈の1,2,3,4と一致しています):
(1)最も簡単なlambda式であり,lambda式のfind_を用いたと考えられる.ifと以下はfunctorのfind_を使用していますifは等価の(2)これは戻り値のあるlambda式で,戻り値の構文は上に示すように,->パラメータリストの括弧の後ろに書く.戻り値は以下の場合は省略できる:a.戻り値がvoidの場合b.lambda式のbodyにreturn exprがあり、exprのタイプは戻り値と同じである.
(3)これはlambda式captureローカルローカル変数の例で、ここで3つの小例は、それぞれcaptureの場合の異なる構文であり、1つ目の小例では=captureを表す変数pass-by-value、2つ目の小出しでは&captureを表す変数pass-by-reference、3つ目の小例ではdefaultを指定したpass-by-valueであるが、max_valueこの単独pass-by-reference (4)これはlambda式captureクラスメンバー変数の例であり,ここでも3つの小さな例がある.1つ目の小さな例はthisポインタによるcaptureメンバー変数であり、2つ目、3つ目はデフォルト方式であるが、2つ目はpass-by-value方式であり、3つ目はpass-by-referenceによるである.
上記の例を分析して、lambda式の使用に関する注意点をまとめます.
(1)lambda式で参照変数を使用するには、a.呼び出しコンテキストにおける局所変数、captureのみが参照できる(上記の例3に示す)b.非局所変数はを直接参照できるという原則を遵守する必要がある.
(2)使用者は、closure(lambda式で生成された呼び出し可能エンティティ)が参照する変数(主にポインタと参照)は、closure呼び出しが完了する前に使用可能であることを保証しなければならないことに注意しなければならない.この点は、上のbindバインドパラメータの後に生成された呼び出し可能エンティティと一致するである.
(3)lambdaの用途については、closureを生成するためのものであり、closureも呼び出し可能なエンティティであるため、std::functionオブジェクトによって生成されたclosureを保存してもよいし、auto を直接使用してもよい.
以上の紹介ではfunction,bind,lambdaの使い方を基本的に理解し,3つを組み合わせるとC++が非常に強くなり,関数的なプログラミングの味がします.最後に、bindでfunctionを生成する場合とlambda式でfunctionを生成する場合、通常は両方ともokですが、パラメータが多い場合、bindは多くのstd::placeholdersに伝わり、lambda式が直感的ではないように見えるので、lambda式の使用を優先することをお勧めします.