【転帖】漫話C++0 x(四)—-function,bindとlambda

8698 ワード

本当にこの文はいつもひっくり返すのがあまりよくないと思います.そこで思い切って回ってきて、原文を見たいのはスタンプを押してください:http://www.wuzesheng.com/?p=2032
本稿は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オブジェクトは、C++の既存の呼び出し可能なエンティティに対する安全なパッケージのタイプが新たに追加されました.(関数ポインタのような呼び出し可能なエンティティは、タイプが安全ではないことを知っています).functionオブジェクトの例をいくつか見てみましょう.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #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)の条件を満たす任意の呼び出し可能エンティティ
  • をrefertoで満たすことができる.
  • (3)std::function objectの最大の用途は関数コールバックを実現することであり、使用者は、等しいまたは等しくない
  • を検査するために使用できないことに注意しなければならない.
  • 2. bind

  •     bindは、指定された呼び出し可能エンティティのいくつかのパラメータを既存の変数にバインドして新しい呼び出し可能エンティティを生成するメカニズムであり、このメカニズムはコールバック関数の使用中にも有用である.C++98には、functorの最初のパラメータと2番目のパラメータをバインドするために使用できる2つの関数bind 1 stとbind 2 ndがあり、いずれもバインド可能であるパラメータを設定します.bind 1 stとbind 2 ndの可用性が大幅に低下するように、様々な制限があります.C++0 xでは、std::bindが提供されています.バインドされたパラメータの個数は制限されず、バインドされた具体的なパラメータも制限されません.ユーザーが指定します.このbindこそ本当の意味でのバインドであり、それがあれば、bind 1 stとbind 2 ndは何の役にも立たないので、C++0 xではbの使用はお勧めしません.ind 1 stとbind 2 ndはdeprecatedです.bindの使い方を例に挙げてみましょう.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #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は、1つの2つのパラメータの一般関数の最初のパラメータを10にバインドし、新しい1つのパラメータを生成する呼び出し可能なエンティティである.bf 2は、1つのクラスメンバー関数をクラスオブジェクトにバインドし、一般関数のような新しい呼び出し可能なエンティティを生成する.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の廬山の正体を見てみましょう.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    
    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,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を生成することについては、通常2つともokですが、パラメータが多い場合はbindが伝わります多くのstd::placeholdersは、lambda式が直感的ではないように見えるので、lambda式の使用を優先することをお勧めします.