C++のラムダ式と関数オブジェクトの使い方を徹底解説

C++は強力で柔軟なプログラミング言語であり、その機能の一つにラムダ式と関数オブジェクトがあります。ラムダ式は、匿名関数として知られ、特定の場所で一時的に使用する小さな関数を作成するために使用されます。一方、関数オブジェクトは、関数のように振る舞うクラスインスタンスを指し、複雑な動作をカプセル化するために使用されます。本記事では、C++のラムダ式と関数オブジェクトの基礎から応用までを詳しく解説し、効果的に使用するための実践的な例を提供します。

目次
  1. ラムダ式の基本構文
    1. ラムダ式の基本構文
    2. 基本的な例
  2. ラムダ式のキャプチャリスト
    1. キャプチャリストの基本構文
    2. 各キャプチャの説明
  3. 関数オブジェクトとは
    1. 関数オブジェクトの基本構文
    2. 関数オブジェクトの特徴
  4. 関数オブジェクトとラムダ式の違い
    1. 定義と可読性
    2. キャプチャ機能
    3. 状態の保持
    4. テンプレートの使用
    5. 利便性と使用場面
  5. ラムダ式の実用例
    1. ソートのカスタマイズ
    2. STLアルゴリズムでの使用
    3. イベントハンドリング
    4. キャプチャによる状態管理
  6. 関数オブジェクトの実用例
    1. カスタム比較関数
    2. 状態を持つ関数オブジェクト
    3. テンプレートを使用した汎用関数オブジェクト
    4. 関数オブジェクトを使ったコールバック
  7. ラムダ式と関数オブジェクトの組み合わせ
    1. ラムダ式を関数オブジェクトのメンバとして使用
    2. 関数オブジェクトのメンバ関数内でラムダ式を使用
    3. ラムダ式で関数オブジェクトを生成
    4. ラムダ式と関数オブジェクトの相互運用
  8. 応用的な使い方
    1. 状態を保持するラムダ式
    2. ジェネリックな関数オブジェクト
    3. ラムダ式によるカスタムアルゴリズム
    4. デコレーター関数オブジェクト
    5. 高度なキャプチャとムーブキャプチャ
  9. 演習問題
    1. 演習問題1: ラムダ式を使ったフィルタリング
    2. 演習問題2: 状態を保持する関数オブジェクト
    3. 演習問題3: ラムダ式と関数オブジェクトの組み合わせ
    4. 演習問題4: ムーブキャプチャ
    5. 演習問題5: デコレーター関数オブジェクト
  10. まとめ

ラムダ式の基本構文

ラムダ式は、C++11以降で導入された匿名関数です。以下の基本構文で定義します。

ラムダ式の基本構文

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

構文の要素

  • キャプチャリスト: ラムダ式の外部から変数をキャプチャする方法を指定します。[]で囲まれたリストで、通常は=&などを使用します。
  • 引数リスト: ラムダ式に渡される引数を指定します。通常の関数の引数と同様に記述します。
  • 戻り値の型: ラムダ式の戻り値の型を指定します。省略可能で、省略した場合はコンパイラが自動的に推論します。
  • 関数の本体: ラムダ式の実行内容を定義します。

基本的な例

以下に、ラムダ式を使った簡単な例を示します。

#include <iostream>

int main() {
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    std::cout << "Sum: " << add(3, 4) << std::endl; // Output: Sum: 7
    return 0;
}

この例では、addというラムダ式を定義し、二つの整数を引数として受け取り、その合計を返します。main関数内でaddを呼び出し、結果を表示しています。

以上が、C++のラムダ式の基本構文とその簡単な例です。次のセクションでは、キャプチャリストの役割と使い方について詳しく解説します。

ラムダ式のキャプチャリスト

キャプチャリストは、ラムダ式が定義されたスコープの外部変数をラムダ式の内部で使用するための方法を指定するものです。これにより、ラムダ式の中で外部変数にアクセスできるようになります。

キャプチャリストの基本構文

キャプチャリストは、[]内にキャプチャする変数やキャプチャの方法を指定します。以下のような形式があります:

  • 値キャプチャ: = を使用して、外部変数を値でキャプチャします。
  • 参照キャプチャ: & を使用して、外部変数を参照でキャプチャします。
  • 個別キャプチャ: a などの変数名を指定して個別にキャプチャします。

キャプチャの例

以下にキャプチャリストを使った例を示します。

#include <iostream>

int main() {
    int x = 10;
    int y = 20;

    // 値キャプチャ
    auto add_value = [=](int a) {
        return x + y + a;
    };

    // 参照キャプチャ
    auto add_ref = [&](int a) {
        return x + y + a;
    };

    // 個別キャプチャ
    auto add_individual = [x, &y](int a) {
        y = 50; // y is modified
        return x + y + a;
    };

    std::cout << "Add Value: " << add_value(5) << std::endl; // Output: Add Value: 35
    std::cout << "Add Ref: " << add_ref(5) << std::endl; // Output: Add Ref: 35
    std::cout << "Add Individual: " << add_individual(5) << std::endl; // Output: Add Individual: 65
    std::cout << "y after Individual: " << y << std::endl; // Output: y after Individual: 50

    return 0;
}

各キャプチャの説明

値キャプチャ

[=] を使うと、ラムダ式は xy を値でキャプチャします。この場合、外部変数のコピーが作成され、ラムダ式内で使用されます。

参照キャプチャ

[&] を使うと、ラムダ式は xy を参照でキャプチャします。これにより、ラムダ式内で外部変数を変更することができます。

個別キャプチャ

[x, &y] を使うと、x は値で、y は参照でキャプチャされます。個別キャプチャを使うことで、特定の変数だけを選択してキャプチャすることが可能です。

以上が、キャプチャリストの基本的な使い方と具体例です。次のセクションでは、関数オブジェクトについて解説します。

関数オブジェクトとは

関数オブジェクト(ファンクター)は、関数のように振る舞うオブジェクトです。これは、関数呼び出し演算子 () をオーバーロードすることで実現されます。関数オブジェクトは、状態を保持しつつ関数のように動作することができるため、より複雑な動作をカプセル化するのに適しています。

関数オブジェクトの基本構文

関数オブジェクトは通常、クラスや構造体として定義されます。以下に基本的な例を示します:

#include <iostream>

// 関数オブジェクトの定義
class Add {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    Add add; // 関数オブジェクトのインスタンスを作成
    std::cout << "Sum: " << add(3, 4) << std::endl; // Output: Sum: 7
    return 0;
}

この例では、Add というクラスを定義し、その中で operator() をオーバーロードしています。このオーバーロードによって Add のインスタンスが関数のように呼び出せるようになります。

関数オブジェクトの特徴

状態を持つことができる

関数オブジェクトは、メンバ変数を持つことができるため、関数呼び出しの際に状態を保持することができます。例えば、以下の例では状態を持つ関数オブジェクトを示しています:

#include <iostream>

// 状態を持つ関数オブジェクトの定義
class Counter {
private:
    int count;
public:
    Counter() : count(0) {}

    int operator()() {
        return ++count;
    }
};

int main() {
    Counter counter;
    std::cout << "Counter: " << counter() << std::endl; // Output: Counter: 1
    std::cout << "Counter: " << counter() << std::endl; // Output: Counter: 2
    std::cout << "Counter: " << counter() << std::endl; // Output: Counter: 3
    return 0;
}

この例では、Counter クラスがカウントを保持し、operator() の呼び出しごとにカウントをインクリメントしています。

柔軟な機能のカプセル化

関数オブジェクトは、複数のメンバ関数やメンバ変数を持つことができるため、より柔軟に機能をカプセル化できます。また、テンプレートを使用することで、ジェネリックな関数オブジェクトを作成することも可能です。

以上が、関数オブジェクトの基本的な概念と使用方法です。次のセクションでは、ラムダ式と関数オブジェクトの違いについて詳しく解説します。

関数オブジェクトとラムダ式の違い

関数オブジェクトとラムダ式はどちらも関数のように扱えるC++の機能ですが、それぞれに異なる特徴と利点があります。以下に、その違いと特徴について解説します。

定義と可読性

関数オブジェクトは通常クラスや構造体として定義されます。一方、ラムダ式は匿名関数として、その場で簡単に定義できます。

// 関数オブジェクトの定義
class Multiply {
public:
    int operator()(int a, int b) const {
        return a * b;
    }
};

// ラムダ式の定義
auto multiply = [](int a, int b) {
    return a * b;
};

関数オブジェクトの定義はコードが長くなりがちですが、ラムダ式は短く、可読性が高いです。

キャプチャ機能

ラムダ式は外部変数をキャプチャすることができますが、関数オブジェクトはコンストラクタで外部変数を受け取る必要があります。

int x = 10;
int y = 20;

// ラムダ式によるキャプチャ
auto add = [x, y](int a) {
    return x + y + a;
};

// 関数オブジェクトによるキャプチャ
class Add {
    int x, y;
public:
    Add(int x, int y) : x(x), y(y) {}
    int operator()(int a) const {
        return x + y + a;
    }
};
Add addObj(x, y);

ラムダ式のキャプチャはシンプルで直感的です。

状態の保持

関数オブジェクトは状態を保持するためのメンバ変数を持つことができますが、ラムダ式は通常その場限りの使い捨てです。

// 関数オブジェクト
class Counter {
    int count;
public:
    Counter() : count(0) {}
    int operator()() {
        return ++count;
    }
};

// ラムダ式
auto counter = [count = 0]() mutable {
    return ++count;
};

関数オブジェクトは状態を持ち続ける用途に適しています。

テンプレートの使用

関数オブジェクトはテンプレートを利用して、型に依存しない汎用的な関数を作成できますが、ラムダ式は特定の型に依存します。

// 関数オブジェクトのテンプレート
template<typename T>
class Adder {
public:
    T operator()(T a, T b) const {
        return a + b;
    }
};

// ラムダ式
auto add = [](int a, int b) {
    return a + b;
};

テンプレートを使うことで、関数オブジェクトは汎用性が高くなります。

利便性と使用場面

ラムダ式は一時的な簡単な操作や、関数に一度だけ渡す処理に適しています。関数オブジェクトは複雑な状態管理や汎用的な関数を必要とする場合に適しています。

以上が、ラムダ式と関数オブジェクトの主な違いです。次のセクションでは、ラムダ式の具体的な実用例について紹介します。

ラムダ式の実用例

ラムダ式は、C++のプログラミングにおいてさまざまな場面で便利に使われます。ここでは、ラムダ式の具体的な実用例を紹介します。

ソートのカスタマイズ

ラムダ式は、標準ライブラリの関数にカスタム比較関数を提供するためにしばしば使用されます。例えば、std::sort関数にラムダ式を渡して、カスタムソートを実現することができます。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5};

    // 降順ソート
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });

    std::cout << "Sorted numbers: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // Output: 9 6 5 5 4 3 2 1 1

    return 0;
}

この例では、std::sort関数にラムダ式を渡し、降順でソートしています。

STLアルゴリズムでの使用

ラムダ式は、標準テンプレートライブラリ(STL)のアルゴリズムと共に使用されることがよくあります。例えば、std::for_each関数にラムダ式を渡して、要素ごとに処理を実行できます。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 各要素を2倍にする
    std::for_each(numbers.begin(), numbers.end(), [](int &n) {
        n *= 2;
    });

    std::cout << "Doubled numbers: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // Output: 2 4 6 8 10

    return 0;
}

この例では、std::for_each関数にラムダ式を渡し、ベクトルの各要素を2倍にしています。

イベントハンドリング

ラムダ式は、GUIプログラミングやイベント駆動型プログラミングにおいて、イベントハンドラとして使用されることがよくあります。

#include <iostream>
#include <functional>

void handleEvent(const std::function<void()> &handler) {
    // イベントが発生したと仮定して、ハンドラを呼び出す
    handler();
}

int main() {
    int count = 0;

    // イベントハンドラとしてラムダ式を使用
    handleEvent([&count]() {
        count++;
        std::cout << "Event handled! Count: " << count << std::endl;
    });

    return 0;
}

この例では、handleEvent関数にラムダ式を渡してイベントハンドラを定義し、イベントが発生した際にラムダ式が実行されます。

キャプチャによる状態管理

ラムダ式は、外部変数をキャプチャすることで状態を管理するのにも適しています。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int sum = 0;
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 各要素をsumに加算
    std::for_each(numbers.begin(), numbers.end(), [&sum](int n) {
        sum += n;
    });

    std::cout << "Sum: " << sum << std::endl; // Output: Sum: 15

    return 0;
}

この例では、ラムダ式が外部変数 sum をキャプチャし、各要素を加算して sum を更新しています。

以上が、ラムダ式の具体的な実用例です。次のセクションでは、関数オブジェクトの具体的な実用例について紹介します。

関数オブジェクトの実用例

関数オブジェクトは、C++のプログラミングにおいて特定の機能をカプセル化し、関数のように使用できる強力なツールです。ここでは、関数オブジェクトの具体的な実用例を紹介します。

カスタム比較関数

関数オブジェクトは、標準ライブラリの関数にカスタム比較関数を提供するために使用されます。例えば、std::sort関数にカスタム比較関数を渡して、ソートをカスタマイズできます。

#include <iostream>
#include <vector>
#include <algorithm>

// カスタム比較関数オブジェクト
class CustomCompare {
public:
    bool operator()(int a, int b) const {
        return a > b; // 降順
    }
};

int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5};

    // カスタム比較関数オブジェクトを使用したソート
    std::sort(numbers.begin(), numbers.end(), CustomCompare());

    std::cout << "Sorted numbers: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // Output: 9 6 5 5 4 3 2 1 1

    return 0;
}

この例では、CustomCompare という関数オブジェクトを定義し、降順でソートしています。

状態を持つ関数オブジェクト

関数オブジェクトは、メンバ変数を持つことができるため、状態を保持しながら動作することができます。以下の例では、カウンタ機能を持つ関数オブジェクトを示します。

#include <iostream>

// カウンタ関数オブジェクト
class Counter {
    int count;
public:
    Counter() : count(0) {}

    int operator()() {
        return ++count;
    }
};

int main() {
    Counter counter;

    std::cout << "Count: " << counter() << std::endl; // Output: Count: 1
    std::cout << "Count: " << counter() << std::endl; // Output: Count: 2
    std::cout << "Count: " << counter() << std::endl; // Output: Count: 3

    return 0;
}

この例では、Counter クラスがカウントを保持し、関数呼び出しのたびにカウントをインクリメントしています。

テンプレートを使用した汎用関数オブジェクト

関数オブジェクトはテンプレートを利用して、型に依存しない汎用的な関数を作成することができます。

#include <iostream>

// テンプレート関数オブジェクト
template<typename T>
class Adder {
public:
    T operator()(T a, T b) const {
        return a + b;
    }
};

int main() {
    Adder<int> intAdder;
    Adder<double> doubleAdder;

    std::cout << "Int sum: " << intAdder(3, 4) << std::endl; // Output: Int sum: 7
    std::cout << "Double sum: " << doubleAdder(3.5, 4.5) << std::endl; // Output: Double sum: 8.0

    return 0;
}

この例では、テンプレートを使用して、整数と浮動小数点数の加算を行う関数オブジェクトを定義しています。

関数オブジェクトを使ったコールバック

関数オブジェクトは、イベント駆動型プログラミングでコールバック関数として使用することができます。

#include <iostream>
#include <functional>

// コールバックを受け取る関数
void handleEvent(const std::function<void()> &callback) {
    // イベントが発生したと仮定して、コールバックを呼び出す
    callback();
}

// コールバック関数オブジェクト
class EventHandler {
public:
    void operator()() const {
        std::cout << "Event handled!" << std::endl;
    }
};

int main() {
    EventHandler eventHandler;

    // 関数オブジェクトをコールバックとして渡す
    handleEvent(eventHandler);

    return 0;
}

この例では、EventHandler という関数オブジェクトを定義し、それをコールバック関数として使用しています。

以上が、関数オブジェクトの具体的な実用例です。次のセクションでは、ラムダ式と関数オブジェクトを組み合わせた使用方法について解説します。

ラムダ式と関数オブジェクトの組み合わせ

ラムダ式と関数オブジェクトは、それぞれ単独でも強力ですが、組み合わせて使用することでさらに柔軟性と機能性を高めることができます。ここでは、両者を組み合わせた具体的な使用方法を紹介します。

ラムダ式を関数オブジェクトのメンバとして使用

関数オブジェクトのメンバとしてラムダ式を持たせることで、柔軟な動作をカプセル化することができます。

#include <iostream>
#include <functional>

// 関数オブジェクト
class LambdaHolder {
public:
    std::function<int(int, int)> func;

    LambdaHolder(std::function<int(int, int)> f) : func(f) {}

    int execute(int a, int b) const {
        return func(a, b);
    }
};

int main() {
    // ラムダ式を関数オブジェクトに渡す
    LambdaHolder adder([](int a, int b) {
        return a + b;
    });

    std::cout << "Result: " << adder.execute(3, 4) << std::endl; // Output: Result: 7

    return 0;
}

この例では、ラムダ式を関数オブジェクトのメンバとして保持し、実行時にそのラムダ式を呼び出しています。

関数オブジェクトのメンバ関数内でラムダ式を使用

関数オブジェクトのメンバ関数内でラムダ式を使用することで、関数オブジェクトの機能を拡張することができます。

#include <iostream>
#include <vector>
#include <algorithm>

// 関数オブジェクト
class Processor {
public:
    void process(std::vector<int>& data) {
        // ラムダ式を使ってデータを処理
        std::for_each(data.begin(), data.end(), [](int &n) {
            n *= 2; // 各要素を2倍にする
        });
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    Processor processor;

    processor.process(numbers);

    std::cout << "Processed numbers: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // Output: Processed numbers: 2 4 6 8 10

    return 0;
}

この例では、Processor 関数オブジェクトがラムダ式を使ってベクトルの各要素を2倍にしています。

ラムダ式で関数オブジェクトを生成

ラムダ式を使って関数オブジェクトを生成し、その場で一時的な処理を行うことができます。

#include <iostream>
#include <vector>
#include <algorithm>

// カスタム関数オブジェクト
class CustomOperation {
public:
    int operator()(int a, int b) const {
        return a * b; // 乗算を行う
    }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    CustomOperation customOp;

    std::transform(numbers.begin(), numbers.end(), numbers.begin(), [&customOp](int n) {
        return customOp(n, 2); // 各要素に対してカスタム操作を適用
    });

    std::cout << "Transformed numbers: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // Output: Transformed numbers: 2 4 6 8 10

    return 0;
}

この例では、std::transform 関数にラムダ式を渡し、ラムダ式内でカスタム関数オブジェクトを使用して各要素に対して操作を行っています。

ラムダ式と関数オブジェクトの相互運用

ラムダ式と関数オブジェクトを相互に利用することで、柔軟なコードを実現できます。以下の例では、ラムダ式を関数オブジェクトのコンストラクタに渡しています。

#include <iostream>
#include <functional>

// 関数オブジェクト
class Combiner {
    std::function<int(int, int)> combineFunc;
public:
    Combiner(std::function<int(int, int)> func) : combineFunc(func) {}

    int combine(int a, int b) const {
        return combineFunc(a, b);
    }
};

int main() {
    // ラムダ式を関数オブジェクトに渡す
    Combiner adder([](int a, int b) {
        return a + b;
    });

    std::cout << "Sum: " << adder.combine(5, 7) << std::endl; // Output: Sum: 12

    return 0;
}

この例では、ラムダ式を関数オブジェクトのコンストラクタに渡し、その関数オブジェクトを使用して計算を行っています。

以上が、ラムダ式と関数オブジェクトを組み合わせた使用方法の具体例です。次のセクションでは、ラムダ式と関数オブジェクトの応用的な使い方について解説します。

応用的な使い方

ラムダ式と関数オブジェクトの応用的な使い方により、C++プログラムの柔軟性と表現力が向上します。ここでは、いくつかの高度な使用方法を紹介します。

状態を保持するラムダ式

ラムダ式は、変数をキャプチャすることで状態を保持することができます。これは、状態を持つ関数オブジェクトと同様の機能を提供します。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int factor = 2;
    auto multiplier = [factor](int n) mutable {
        factor++;
        return n * factor;
    };

    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::transform(numbers.begin(), numbers.end(), numbers.begin(), multiplier);

    std::cout << "Transformed numbers: ";
    for (int n : numbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl; // Output: Transformed numbers: 2 6 12 20 30

    return 0;
}

この例では、ラムダ式がキャプチャした変数 factor を変更することで、状態を保持しながら処理を行っています。

ジェネリックな関数オブジェクト

関数オブジェクトはテンプレートを使用してジェネリックにすることができます。これにより、さまざまな型に対して同じ関数オブジェクトを使用できます。

#include <iostream>
#include <vector>
#include <algorithm>

// ジェネリック関数オブジェクト
template<typename T>
class Multiply {
public:
    T operator()(T a, T b) const {
        return a * b;
    }
};

int main() {
    Multiply<int> intMultiplier;
    Multiply<double> doubleMultiplier;

    std::cout << "Int result: " << intMultiplier(3, 4) << std::endl; // Output: Int result: 12
    std::cout << "Double result: " << doubleMultiplier(3.5, 2.0) << std::endl; // Output: Double result: 7.0

    return 0;
}

この例では、Multiply クラスがテンプレートとして定義されており、異なる型の乗算を実行できます。

ラムダ式によるカスタムアルゴリズム

ラムダ式を使用してカスタムアルゴリズムを作成し、複雑な操作を簡潔に記述することができます。

#include <iostream>
#include <vector>
#include <numeric>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // カスタムアルゴリズム:すべての要素の積を計算
    int product = std::accumulate(numbers.begin(), numbers.end(), 1, [](int a, int b) {
        return a * b;
    });

    std::cout << "Product of numbers: " << product << std::endl; // Output: Product of numbers: 120

    return 0;
}

この例では、std::accumulate 関数にラムダ式を渡して、ベクトル内のすべての要素の積を計算しています。

デコレーター関数オブジェクト

関数オブジェクトを使って、関数の前後に追加の操作を行うデコレーターを作成することができます。

#include <iostream>
#include <functional>

// デコレーター関数オブジェクト
class Logger {
    std::function<void()> func;
public:
    Logger(std::function<void()> f) : func(f) {}

    void operator()() const {
        std::cout << "Start of function" << std::endl;
        func();
        std::cout << "End of function" << std::endl;
    }
};

int main() {
    auto myFunc = []() {
        std::cout << "Inside function" << std::endl;
    };

    Logger loggedFunc(myFunc);
    loggedFunc(); // Output: Start of function \n Inside function \n End of function

    return 0;
}

この例では、Logger クラスが関数オブジェクトをデコレーターとして使用し、関数の前後にログを追加しています。

高度なキャプチャとムーブキャプチャ

C++14以降では、ラムダ式でムーブキャプチャを使用して、所有権をラムダ式に移すことができます。

#include <iostream>
#include <vector>
#include <memory>

int main() {
    auto ptr = std::make_unique<int>(10);

    auto moveCapture = [p = std::move(ptr)]() {
        std::cout << "Captured value: " << *p << std::endl;
    };

    moveCapture(); // Output: Captured value: 10
    // ptrは既にムーブされたため使用できない

    return 0;
}

この例では、ムーブキャプチャを使用してユニークポインタの所有権をラムダ式に移し、その後ラムダ式内で使用しています。

以上が、ラムダ式と関数オブジェクトの応用的な使い方です。次のセクションでは、ラムダ式と関数オブジェクトの理解を深めるための演習問題を提供します。

演習問題

ラムダ式と関数オブジェクトの理解を深めるために、いくつかの演習問題を提供します。これらの問題に取り組むことで、実際のプログラミングにおけるラムダ式と関数オブジェクトの使い方をより深く理解できるようになります。

演習問題1: ラムダ式を使ったフィルタリング

与えられた整数のリストから、偶数だけを抽出するプログラムをラムダ式を用いて作成してください。

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> evenNumbers;

    // ラムダ式を使って偶数をフィルタリング
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(evenNumbers), [](int n) {
        return n % 2 == 0;
    });

    std::cout << "Even numbers: ";
    for (int n : evenNumbers) {
        std::cout << n << " ";
    }
    std::cout << std::endl;

    return 0;
}

演習問題2: 状態を保持する関数オブジェクト

カウント機能を持つ関数オブジェクトを作成し、そのオブジェクトを使ってカウントをインクリメントするプログラムを書いてください。

#include <iostream>

// カウンタ関数オブジェクトを作成
class Counter {
    int count;
public:
    Counter() : count(0) {}

    int operator()() {
        return ++count;
    }
};

int main() {
    Counter counter;

    std::cout << "Count: " << counter() << std::endl; // Output: Count: 1
    std::cout << "Count: " << counter() << std::endl; // Output: Count: 2
    std::cout << "Count: " << counter() << std::endl; // Output: Count: 3

    return 0;
}

演習問題3: ラムダ式と関数オブジェクトの組み合わせ

関数オブジェクトのメンバとしてラムダ式を使用し、与えられた二つの整数の和を計算するプログラムを作成してください。

#include <iostream>
#include <functional>

// 関数オブジェクト
class LambdaHolder {
public:
    std::function<int(int, int)> func;

    LambdaHolder(std::function<int(int, int)> f) : func(f) {}

    int execute(int a, int b) const {
        return func(a, b);
    }
};

int main() {
    // ラムダ式を関数オブジェクトに渡す
    LambdaHolder adder([](int a, int b) {
        return a + b;
    });

    std::cout << "Result: " << adder.execute(3, 4) << std::endl; // Output: Result: 7

    return 0;
}

演習問題4: ムーブキャプチャ

ユニークポインタをラムダ式にムーブキャプチャして、その値を出力するプログラムを書いてください。

#include <iostream>
#include <memory>

int main() {
    auto ptr = std::make_unique<int>(10);

    auto moveCapture = [p = std::move(ptr)]() {
        std::cout << "Captured value: " << *p << std::endl;
    };

    moveCapture(); // Output: Captured value: 10
    // ptrは既にムーブされたため使用できない

    return 0;
}

演習問題5: デコレーター関数オブジェクト

関数オブジェクトを使って、関数の前後にログを出力するデコレーターを作成してください。

#include <iostream>
#include <functional>

// デコレーター関数オブジェクト
class Logger {
    std::function<void()> func;
public:
    Logger(std::function<void()> f) : func(f) {}

    void operator()() const {
        std::cout << "Start of function" << std::endl;
        func();
        std::cout << "End of function" << std::endl;
    }
};

int main() {
    auto myFunc = []() {
        std::cout << "Inside function" << std::endl;
    };

    Logger loggedFunc(myFunc);
    loggedFunc(); // Output: Start of function \n Inside function \n End of function

    return 0;
}

これらの演習問題に取り組むことで、ラムダ式と関数オブジェクトの実践的な使い方を習得できます。次のセクションでは、これまでの内容のまとめを行います。

まとめ

本記事では、C++のラムダ式と関数オブジェクトについて詳しく解説しました。ラムダ式は、匿名関数として簡潔に関数を定義できる便利な機能であり、キャプチャリストを利用することで外部変数を内部に取り込むことができます。関数オブジェクトは、状態を保持しつつ関数のように振る舞うオブジェクトで、テンプレートを利用して汎用性を持たせることも可能です。

ラムダ式と関数オブジェクトの違いや利点を理解し、それぞれの用途に応じて適切に使い分けることで、C++プログラムの柔軟性と表現力を高めることができます。また、応用的な使い方や演習問題に取り組むことで、実践的なスキルを磨くことができるでしょう。

これらの機能を効果的に活用し、C++プログラミングの幅を広げてください。

コメント

コメントする

目次
  1. ラムダ式の基本構文
    1. ラムダ式の基本構文
    2. 基本的な例
  2. ラムダ式のキャプチャリスト
    1. キャプチャリストの基本構文
    2. 各キャプチャの説明
  3. 関数オブジェクトとは
    1. 関数オブジェクトの基本構文
    2. 関数オブジェクトの特徴
  4. 関数オブジェクトとラムダ式の違い
    1. 定義と可読性
    2. キャプチャ機能
    3. 状態の保持
    4. テンプレートの使用
    5. 利便性と使用場面
  5. ラムダ式の実用例
    1. ソートのカスタマイズ
    2. STLアルゴリズムでの使用
    3. イベントハンドリング
    4. キャプチャによる状態管理
  6. 関数オブジェクトの実用例
    1. カスタム比較関数
    2. 状態を持つ関数オブジェクト
    3. テンプレートを使用した汎用関数オブジェクト
    4. 関数オブジェクトを使ったコールバック
  7. ラムダ式と関数オブジェクトの組み合わせ
    1. ラムダ式を関数オブジェクトのメンバとして使用
    2. 関数オブジェクトのメンバ関数内でラムダ式を使用
    3. ラムダ式で関数オブジェクトを生成
    4. ラムダ式と関数オブジェクトの相互運用
  8. 応用的な使い方
    1. 状態を保持するラムダ式
    2. ジェネリックな関数オブジェクト
    3. ラムダ式によるカスタムアルゴリズム
    4. デコレーター関数オブジェクト
    5. 高度なキャプチャとムーブキャプチャ
  9. 演習問題
    1. 演習問題1: ラムダ式を使ったフィルタリング
    2. 演習問題2: 状態を保持する関数オブジェクト
    3. 演習問題3: ラムダ式と関数オブジェクトの組み合わせ
    4. 演習問題4: ムーブキャプチャ
    5. 演習問題5: デコレーター関数オブジェクト
  10. まとめ