呼び出し可能オブジェクト

6624 ワード

A successful book is not made of what is in it, but what is left out of it.
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ ㅤㅤㅤ— Mark Twain C++ 2.0に触れてからもうしばらく経ちましたが、C++で関数と考えられているもの、あるいは関数に似ているものを簡単にまとめてみましょう.標準的なCとC++関数から関数オブジェクトとlambda式までゆっくり話します.
1.一般的な関数C++には関数を定義する方法がたくさんありますが、以下に例を挙げて説明します.よく知られている概念がいくつかあります.
  • 標準C関数
  • bool greater(int arg1, int arg2) { return arg1 > arg2; }
  • クラスメンバー関数
  • struct number {
      bool greater(int arg1, int arg2) { return arg1 > arg2; }
    };

    説明すると、C++の中で私は一般的にstructを使ってクラスを定義するだけで、伝統的なclassを使うのではなく、後の私のすべての文字はこのようにして、これは純粋に個人のプログラミングスタイルで、読者は自分の習慣を維持して、いつものように堅持すればいいです.
  • クラス静的関数
  • struct number {
      static bool greater(int arg1, int arg2) { return arg1 > arg2; }
    };

    一般的な関数,クラスのメンバー関数,クラスの静的手法については,読者がよく知っていると信じているが,C++ 2.0の新しい関数定義を紹介する.
  • C++ 2.0の新しい関数
  • c++11は、関数の最後に関数の戻りタイプを説明する新しい関数の書き方を提供し、関数の最初にautoキーワードを使用します.この使用法は主に関数テンプレートの作成に使用されます.
    // C++ 11
    auto greater(int arg1, int arg2) -> bool {    /*        */
        return arg1 > arg2;
    }

    上記の使い方はあまり使われていませんが、c++14以降は戻り値のタイプを完全に無視することができますが、コンパイラがreturn文に基づいて自動的に推定します.ここでは関連内容が多く、詳細には展開されません.簡単に2つの例を挙げます.
    // C++ 14
    int value = 3;
    auto answer()  { return value ; }     /*      int */
    const auto& answer()  { return value ; }     /*      const int& */
    
    //       decltype    
    decltype(auto) answer()  { return value ; }

    2.関数ポインタ
    関数ポインタ(function pointer)関数アドレスを格納する変数で、この変数で関数を呼び出すことができます.c++11の前に、一般にtypedef キーワードを使用して関数ポインタタイプを定義し、c++11の後に、より表現力のあるusingを使用して置き換えることができる.
    bool greater(int arg1, int arg2) { return arg1 > arg2; }
    
    // C++11  
    typedef bool (*old_cmp)(int, int);
    old_cmp cmp = greater;
    
    // C++11  
    using new_cmp = bool (*)(int, int);
    new_cmp  cmp = greater;

    3.擬似関数
    実際には、C++において、関数のようなオブジェクトを定義し、使用することができ、それらは擬似関数(Functor)と呼ばれる.本質的には、リロードされた呼び出しオペレータのクラス(call operator)、すなわちoperator()のクラスを定義し、任意の数と任意のタイプのパラメータを持つことができる.
    struct functor { 
        return_type operator()(args...) const { ... }
    };

    また、operator()に含まれる0個、1個または2個のパラメータに従って、このFunctorをそれぞれジェネレータ、1元模倣関数または2元模倣関数と呼び、以下にそれぞれ例を挙げて説明する.
  • ジェネレータ
  • struct increase_generator { 
      int operator()() noexcept { return num++; }
    private:
      int num = 0;
    };

    動作原理は非常に簡単で、increase_generator::operator()が呼び出されるたびに、メンバー変数numの値が返され、numの値が1増加します.
    int main() {
      increase_generator num_generator;
      for (int i = 0; i < 3; ++i) {
        std::cout << num_generator() << std::endl;
      }
    }
    // output: 0 1 2
  • 一元シミュレーション関数
  • struct cube { 
      constexpr int operator()(const int value) const noexcept { return value * value * value; }
    };

    名前の通り、このシミュレーション関数は伝達された値に対して立方演算を行い、このoperator()はconstとして宣言され、その行為は数学上の純粋な関数、すなわち副作用がないことに似ている.ここのconstexprの役割は、興味のある読者が自分で研究することができます.
  • 述語
  • 一元シミュレーション関数の一般的な用途は、述語(predict)として使用される.すなわち、パラメータが1つしかなく、boolの値を返すシミュレーション関数である.次のようになります.
    struct is_even { 
      constexpr bool operator()(const int value) const noexcept { return (value % 2) == 0; }
    };

    使用例を挙げる
    int main() {
      std::vector numbers{1, 2, 3, 4, 5};
      numbers.erase(std::remove_if(std::begin(numbers), std::end(numbers), is_even()), 
                    std::end(numbers));
    
      std::copy(std::begin(numbers), std::end(numbers), std::ostream_iterator{std::cout, " "});
      return 0;
    }
    // output: 1 3 5 

    上記の例では、Erase-removeの慣用法を使用し、私たちが定義したis_evenのシミュレーション関数と組み合わせて、vectorの偶数要素の削除を実現しました.
  • 二元模倣関数
  • struct greater { 
      bool operator()(const auto& v1, const auto& v2) const noexcept { return v1 > v2; }
    };

    4. lambda
  • を見てください
    私たちは上で実現したシミュレーション関数is_evenを、同様にlambdaで実現し、以下のようにします.
    auto is_even = [] (auto item) { return (item % 2) == 0; };
    
    is_even(2);  //   true
    lambdaを用いた実装はより短く、表現力もより豊富であることがわかるが、通常、lambda式の使用は、適用時に実装されるインライン実装、すなわち実装される.注意:上の構文はC++14以上のコンパイラをサポートする必要があります.
  • 構文
  • [capture list] (param list) -> return_type { lambda body;}

    説明
  • [capture list]外層変数
  • をキャプチャするためのキャプチャリスト
    []任意の変数を取り込む[&]外部作用ドメイン内のすべての変数を取り込み、参照として匿名関数体において[=]を用いて外部作用ドメイン内のすべての変数を取り込み、匿名関数体において[x,&y]xを用いる値で取り込み、yを参照で取り込む[&,x]xを値で取り込むコピーする.その他の変数は参照によって取り込む[=,&y]yは参照によって取り込む.他の変数は、現在のクラスのthisポインタを値で取得します.すでに使用されている場合は、&または=デフォルトでこのオプションを追加します.
  • (param list)パラメータリスト
  • 匿名関数にパラメータがない場合は、(param list)部分を省略できます.
  • -> return_type戻り値タイプ
  • C++14以降は省略可能
  • { lambda body;}関数実装
  • 5.std::functionパッケージ関数オブジェクトstd::functionは呼び出し可能オブジェクトパッケージであり、上述したすべての呼び出し可能オブジェクトを収容するクラステンプレートであり、関数、関数オブジェクト、関数ポインタを統一的に処理し、それらの実行を保存および遅延させることができる.
  • 作用
  • 関数ポインタ(一般関数、クラスメンバー関数、クラス静的メンバー関数を含む)、シミュレーション関数、lambda式をタイプ削除します.つまり、これらの呼び出し可能なエンティティをstd::functionタイプに変換できます.
  • フォーマットの定義:std::function
    bool greater_global(int arg1, int arg2) { return arg1 > arg2; }   //     
    
    struct number {
      bool greater_member(int arg1, int arg2) { return arg1 > arg2; }  //      
      static bool greater_static(int arg1, int arg2) { return arg1 > arg2; } //      
    };
    
    //    
    struct greater_functor { 
      bool operator()(int arg1, int  arg2) const noexcept { return arg1> arg2; }
    };
    
    auto greater_lambda = [] (int arg1, int arg2) { return arg1 > arg2; };  // lambda
    auto greater_lambda2 = [] (auto arg1, auto arg2) { return arg1 > arg2; };  //    lambda
    
    int main() {
      std::function test_function;
      test_function = greater_global;
    
      number object;
      test_function = std::bind(&number::greater_member, &object, 
                                std::placeholders::_1, std::placeholders::_2);
      test_function = std::bind(&number::greater_static, 
                                std::placeholders::_1, std::placeholders::_2);
    
      test_function = greater_lambda ;
      test_function = greater_lambda2;
      test_function = greater_functor();
      
      test_function(3, 2);
    }

    6. End
    勉強を続ける...