C++11の新機能⑤ | ファンクタとラムダ式

目次

1 はじめに

2. ファンクター

3. ラムダ式

3.1. ラムダ式の一般形式

3.2. 戻り値の型の説明

3.3. リストを取得するためのルール

3.4. どのような変数をキャプチャできますか?

3.5. ラムダ式がプログラミングにもたらす利便性


VC++ の共通機能開発の概要 (コラム記事のリスト、購読歓迎、継続的な更新...) icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585 C++ ソフトウェア異常トラブルシューティング チュートリアル シリーズ入門から習熟まで(コラム記事一覧)、ぜひ購読して更新を続けてください...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931入門から習熟までのC++ソフトウェア解析ツール事例集(コラム)記事は更新中...) icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795 C/C++ の基礎と応用 (コラム記事、継続的に更新中...) icon-default.png?t=N7T8https://blog.csdn.net /chenlycly/category_11931267.html        C++ 11 の新機能は非常に重要です。C++ 開発者として、それらを学習する必要があります。これらは筆記試験や面接で取り上げられるだけでなく、オープン ソースでも大規模に使用されていますコード。多くのビデオ会議やライブ ブロードキャスト ソフトウェアで使用されているオープン ソースの WebRTC プロジェクトを例に挙げます。WebRTC コードは C++11 以降の新機能を広範囲に使用しています。そのソース コードを理解するには、C++ のこれらの新機能を理解する必要があります。したがって、次の期間では、参考または参照のために、私の仕事の実践を組み合わせて、C++11 の新機能について詳しく説明します。

1 はじめに

       C++ では、関数名、関数ポインター、ファンクターを使用して関数呼び出しを実装できます。C++11 では、匿名関数とも呼ばれるラムダ式が導入されています。これにより、新しい関数呼び出しメソッドが導入され、プログラミングに大きな利便性がもたらされます。今日はファンクターとラムダ式について話します。

2. ファンクター

       ファンクターは、C++11 では導入されなかったが、以前から存在していた特別なクラスまたは構造体です。関数呼び出し演算子 Operator() をオーバーロードし、関数のように呼び出すことができ、独自のデータ メンバーやメンバー関数を持つこともできます。ファンクターは通常、アルゴリズム (並べ替え、検索、変換など) およびコンテナー (セット、マップ、リストなど) で使用され、カスタマイズされた操作動作を提供します。C++11 では、ラムダ式を使用して単純なファンクターを実装できます。

       以下は、クラス内でのoperator()のオーバーロードの例です。

class MyFunctor
{
public:
    MyFunctor(int tmp) : round(tmp) {}
    int operator()(int tmp) { return tmp + round; }
private:
    int round;
};

int main()
{
    int round = 2;
    MyFunctor f(round); //调用构造函数
    cout << "result = " << f(1) << endl; // operator()(int tmp)

    return 0;
}

クラス オブジェクトを通じてオーバーロードされたoperator() メソッドを呼び出します。C++ では、クラス内の Operator() 演算子をオーバーロードすることにより、通常の関数の代わりに関数オブジェクトを使用します。

       数値のサイズを比較する別の例を見てみましょう。

class compare_class
{
    public:
    bool operator() (int A, int B) const{return A < B;}
};
 
// Declaration of C++ sorting function.
template<class ComparisonFunctor>
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);
 
int main()
{
    int items[]={4, 3, 1, 2};
    compare_class functor;
    sort_ints( items, sizeof(items)/sizeof(items[0]), functor);
}

3. ラムダ式

       匿名関数としても知られるラムダ式は、名前のない (匿名の) インライン関数として理解できる呼び出し可能なコード単位です。通常の関数と同様に、ラムダ式には戻り値の型、パラメータ リスト、および関数本体があります。ただし、関数とは異なり、ラムダ式は関数内で直接定義されます。

       ラムダ式の導入により、プログラミングに大きな利便性がもたらされ、コーディングが大幅に簡素化されます。

3.1. ラムダ式の一般形式

ラムダ式の一般的な形式は次のとおりです。

[キャプチャリスト](パラメータリスト) -> 戻り値の型 { 関数本体 }

で:

1) キャプチャ リスト (キャプチャ リスト)、この式が配置されている関数のローカル変数のリスト、
2) パラメータ リスト (パラメータ リスト)、このラムダ式に渡されるパラメータ リスト、
3) 戻り値の型 (return type) )、このラムダ式の戻り値の型です (省略可能)、
4) 関数本体 (関数本体)、このラムダ式の内部実装です。

       通常の関数の場合、戻り値の型は関数の先頭に配置されますが、ラムダ式の戻り値の型はその形式上先頭に配置できず、末尾のメソッドを使用して戻り値の型を指定する必要があります。パラメーター リストと戻り値の型は無視できますが、次のようなキャプチャ リストと関数本体を含める必要があります。

auto f = [] { return 20; };

3.2. 戻り値の型の説明

       ラムダ式で戻り値の型を直接指定しても問題ありません。戻り値の型が指定されていない場合に何が起こるかを見てみましょう。

       ラムダ式の関数本体に return ステートメントが 1 つだけ含まれており、ラムダ式の戻り値が指定されていない場合、この式の戻り値の型はコンパイル時の return ステートメントの内容に基づいて推定されます。データを追加するためのラムダ式:

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

このラムダには関数の戻り値の型が指定されていませんが、式 (a+b) より、このラムダの戻り値の型は int であることが推測できます。

       ラムダ式に return ステートメントが 1 つだけ含まれていない (複数のステートメント) 場合、コンパイラはコンパイル中にラムダの戻り値の型が void であると判断します。たとえば、数値の絶対値を返すラムダは次のとおりです。

[] ( int a ) { if ( a < 0) return -a; else return a; };

複数のステートメントが含まれているため、コンパイラはラムダが void を返すと判断し、コンパイル中にエラーが報告されます。戻り値の型は void ですが、return で int 型を返すと矛盾しているため、エラーが報告されます。

3.3. リストを取得するためのルール

       キャプチャ リストには、関数のどのローカル変数へのアクセスと、その変数へのアクセス方法が含まれます。変数にアクセスする主な方法は、参照または値によるものです。ラムダによってアクセスされる変数がキャプチャ リストにリストされている場合、それは明示的キャプチャであり、リストされていない場合、それは暗黙的キャプチャです。

       キャプチャ リストの完全なルールを以下に示します。

キャプチャタイプ 説明する
[] 空のキャプチャリスト。関数内の変数は、ラムダ式の内部では使用されません。関数内の変数は、キャプチャ リストが空でない場合にのみ使用できます。
[名前] names は、ラムダが配置されている関数内のローカル変数の名前である名前のカンマ区切りのリストです。デフォルトでは、キャプチャ リスト内の変数値がコピーされます。変数の前に & を追加できます。& を追加すると、参照キャプチャが使用されることを示します。
[&] 暗黙的なキャプチャ リストにより、コンパイラはラムダ内のコードに基づいて関数のどのローカル変数が使用されているかを推測できます。参照キャプチャを使用すると、関数のラムダ本体で使用されるすべての変数が参照によって使用されます。
[=] 暗黙的なキャプチャ リストにより、コンパイラはラムダ内のコードに基づいて関数のどのローカル変数が使用されているかを推測できます。値キャプチャを使用すると、関数から使用される変数の値がラムダ本体にコピーされます。
[&, 識別子リスト]  identifier_list ( & 参照キャプチャのない特殊なケースとして理解されます) は、その変数が配置されている関数からの 0 個以上の変数を含むコンマ区切りのリストです。これらの変数は値によってキャプチャされます。 identifier_list リスト内の名前の前に & を付けることはできませidentifier_list リストで指定されたもの以外の暗黙的にキャプチャされた変数は、参照によってキャプチャされます。
[=, 識別子リスト] identifier_listt (= value キャプチャを使用しない特殊なケースとして理解されます) は、関数が配置されている関数からの 0 個以上の変数を含むカンマ区切りのリストです。これらの変数は参照によってキャプチャされます。identifier_list リスト内の名前は前に付ける必要があります& によってidentifier_list リストで指定されたもの以外の暗黙的にキャプチャされた変数は、値キャプチャを使用します。

キャプチャ リストの例は次のとおりです。

int main()
{
    int a = 0, b = 1;
    auto f1 = []{ return a; };      // error, 没有捕获外部变量
    auto f2 = [=]{ return a; };     // ok, 值传递方式捕获所有外部变量
    auto f3 = [=]{ return a++; };   // error, a是以赋值方式捕获的,无法修改
    auto f4 = [=]() mutable { return a++; };   // ok, 加上mutable修饰符后,可以修改按值传递进来的拷贝
    auto f5 = [&]{ return a++; };              // ok, 引用传递方式捕获所有外部变量, 并对a执行自加运算
    auto f6 = [a]{ return a+b; };              // error, 没有捕获变量b
    auto f9 = [a,&b]{ return a+(b++); };       // ok, 捕获a, &b
    auto f8 = [=,&b]{ return a+(b++); };       // ok, 捕获所有外部变量,&b
    auto f9 = [&,&b]{ return a+(b++); };       // error, 捕获所有外部变量,不能使用&b,b是特例,和默认的引用捕获不一样,使用值捕获,所以不能加&
    auto f10 = [=,b]{ return a+(b++); };       // error, 捕获所有外部变量,不能使用b,b是特例,和默认的值引用不一样,使用引用捕获,前面必须加&
    return 0;
}

3.4. どのような変数をキャプチャできますか?

       一般に、ラムダ式は関数内に配置され、つまり関数に埋め込まれ、ラムダ式が配置されている関数のローカル変数には、ラムダ関数の実装本体でアクセスできます。ラムダ式が配置されている関数がクラスのメンバー関数である場合、ラムダは現在の関数が配置されているクラスのメンバー変数にもアクセスできますが、これを知らない人も多いかもしれません。例えば:

class Test
{
public:
    int i = 0;   // 类的成员变量

    void func(int x, int y)
    {
        auto x1 = []{ return i; };          // error, 没有捕获外部变量
        auto x2 = [=]{ return i+x+y; };     // ok, 值传递方式捕获所有外部变量
        auto x3 = [=]{ return i+x+y; };     // ok, 引用传递方式捕获所有外部变量
        auto x4 = [this]{ return i; };      // ok, 捕获this指针
        auto x5 = [this]{ return i+x+y; };  // error, 没有捕获x, y
        auto x6 = [this, x, y]{ return i+x+y; };// ok, 捕获this指针, x, y
        auto x9 = [this]{ return i++; };        // ok, 捕获this指针, 并修改成员的值
    }
};

3.5. ラムダ式がプログラミングにもたらす利便性

       ラムダ式の導入により、プログラミングに大きな利便性がもたらされ、コーディングが大幅に簡素化されます。たとえば、STL コンテナの find_if、count_if、sort などのアルゴリズム関数を使用する場合、条件関数や比較関数を渡す必要がありますが、これらの関数はラムダ式を使用して直接実装されるため、非常に便利です。
       過去にラムダ式がなかったときは、これらの条件関数と比較関数は、グローバル関数として定義されるか、静的関数として定義されるか、あるいは補助的な all 変数または静的変数として定義されて、関数の外部に実装する必要がありました。たとえば、デバイス情報を格納する構造体があり、次にデバイス情報を格納するベクトル リストがある場合、リストを初期化するだけです。

// 设备信息结构体
typedef struct tagDeviceInfo
{
    char szDeviceId[64];   // 设备id
    char szDeviceName[64]; // 设备名称
    int nDevType;          // 设备类型

public:
    tagDeviceInfo(){ memset(this, 0, sizeof(tagDeviceInfo)); }
}TDeviceInfo;

// 设备管理类
class CDeviceManage
{
    CDeviceManage();
    ~CDeviceManage();

    void InitDeviceList();

private:
    vector<TDeviceInfo> m_vtDevList;
}

// 初始化设备列表(仅用于测试,随意初始化了一些数据)
void CDeviceManage::InitDeviceList()
{
    for ( int i = 0; i < 10; i++ )
    {
        TDeviceInfo tDevInfo;

        char szBuf[128] = { 0 };
        sprintf(szBuf, "E40CF3E4-CC2B-437F-A4B9-65F2D5BD071%d", i);
                                   strcpy(tDevInfo.szDeviceId, szBuf);

        CUIString strName;
        sprintf(szBuf, "设备%d", i);
                                   strcpy(tDevInfo.szDeviceName, szBuf);

        m_vtDevList;.push_back(tDevInfo);
    }
}

CDeviceManage::FindTest メンバー関数で STL アルゴリズム関数 find_if を呼び出して、デバイス GUID (szDeviceId) が E40CF3E4-CC2B-437F-A4B9-65F2D5BD0715 であるデバイス情報を m_vtDevList リストで検索するとします。ラムダ式を使用しない場合は、以下に示すように、条件付き関数の実装を CDeviceManage クラスの外部でグローバル関数として定義し、ターゲット ID を格納する変数をグローバルとして定義する必要があります。

char* s_lpszTargetDevId = ""; // 定义成静态变量

// 将条件匹配函数定义在类CDeviceManage外部,定义成全局函数
BOOL MatchFunc(TDeviceInfo& tDevInfo)
{
    return strcmp( s_lpszTargetDevId,tDevInfo.szDeviceId ) == 0;
}

bool CDeviceManage::FindTest()
{
     // 对静态变量s_pTargetDevId进行赋值
     s_lpszTargetDevId = "E40CF3E4-CC2B-437F-A4B9-65F2D5BD0715";
     vector<TDeviceInfo>::iterator itor = std::find_if(m_vtDevList.begin(), m_vtDevList.end(), MatchFunc);
     if ( itor != m_vtDevList.end() )
     {
         // 找到对应的设备信息,进行后续处理的代码省略
         // ......

         return true;
     }

     return false;
}

上記のコードは実装が比較的面倒です。
       ラムダ式を使用すると、コードが非常に単純になります。条件一致関数をグローバル関数として定義する必要がなく、静的補助変数 s_lpszTargetDevId を定義する必要もありません。ラムダ式を使用して実装されたコードは次のとおりです。 :

bool CDeviceManage::FindTest()
{
    char* lpszTargetDevId = "E40CF3E4-CC2B-437F-A4B9-65F2D5BD0715";
    vector<TDeviceInfo>::iterator itor = std::find_if(vtDevList.begin(), vtDevList.end(), [=](const TDeviceInfo& tDevInfo){
    return strcmp(lpszTargetDevId, tDevInfo.achDeviceId) == 0; } );

    // 后续代码省略
    // ......
}

        STL のアルゴリズム関数を使用して検索する必要がある理由については、STL のアルゴリズム関数の方が効率的であり、for ループを直接走査するよりもはるかに効率的であるためです。STL リストに大量のデータが格納されている場合は、データ検索を効率的に行う必要があり、直接の for ループ変数よりも数桁高い STL アルゴリズム関数を使用する必要があります。これは、プロジェクト!STL アルゴリズム関数を使用して検索効率を向上させる方法に関する記事については、以前の記事を参照してください。

VC++ は STL アルゴリズム関数を呼び出して、STL リストの検索速度を効果的に向上させます (ソース コードが添付されています) icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/123943134 VC++ が C++ STL 標準テンプレート ライブラリでアルゴリズム関数を使用する方法 (ソース コード)コード添付)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125486409

おすすめ

転載: blog.csdn.net/chenlycly/article/details/132776343