Menu
Coddy logo textTech

std::functionとstd::bind

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 86/104。

lambda は強力ですが、異なる型の呼び出し可能オブジェクトを統一的な方法で保存したり、既存の関数を必要なシグネチャに合わせて適応させたりする必要がある場合があります。<functional> ヘッダーの std::functionstd::bind が、これらの問題を解決します。

std::functionは、特定のシグネチャに一致する任意の呼び出し可能なもの(function、lambda、または関数オブジェクト)を保持できる型消去ラッパーです。

#include <iostream>
#include <functional>

int add(int a, int b) { return a + b; }

int main() {
    std::function<int(int, int)> operation;
    
    operation = add;                              // 通常の関数
    std::cout << operation(3, 4) << "\n";        // 7
    
    operation = [](int a, int b) { return a * b; }; // ラムダ
    std::cout << operation(3, 4) << "\n";        // 12
}

std::bindは、既存のfunctionの一部のargumentsを固定して、新しい呼び出し可能オブジェクトを作成します。変数として残すargumentsを示すには、std::placeholders::_1_2などを使用します。

#include <iostream>
#include <functional>

void greet(const std::string& greeting, const std::string& name) {
    std::cout << greeting << ", " << name << "!\n";
}

int main() {
    using namespace std::placeholders;
    
    auto sayHello = std::bind(greet, "Hello", _1);
    sayHello("Alice");  // こんにちは、Alice!
    
    auto swapped = std::bind(greet, _2, _1);
    swapped("Bob", "Hi");  // やあ、Bob!
}

これらのツールは、OOPでコールバックをクラスメンバーとして保存したり、Strategyパターンを実装したりする場合に特に便利です。ただし、現代のC++では、可読性とパフォーマンスが向上するため、std::bindよりもラムダ式が好まれることがよくあります。引数の順序を入れ替える必要がある場合や、レガシーコードを扱う場合に主にstd::bindを使用してください。

challenge icon

チャレンジ

簡単

std::functionstd::bind の力を示す、設定可能な電卓を構築しましょう。実行時に数学演算を保存、交換、カスタマイズできるシステムを作成します。これらのツールによって柔軟なコールバック管理が可能になることを示します。

コードを3つのファイルに分けて構成します。

  • MathOperations.h:演算ライブラリとして機能する、独立した数学関数のコレクションを定義します。

    次の関数を作成します。

    • add(int a, int b):合計を返す
    • subtract(int a, int b):差(a - b)を返す
    • multiply(int a, int b):積を返す
    • power(int base, int exponent, int multiplier)multiplier * (base ^ exponent) を返す。べき乗の計算には単純なループを使用します(exponent は負でないものとします)。
  • Calculator.hstd::function を使用して演算を動的に保存および実行する Calculator クラスを定義します。

    Calculator には次の要素を含めます。

    • 現在の二項演算を保存するプライベートメンバー std::function<int(int, int)>
    • setOperation(std::function<int(int, int)> op):現在の演算を設定する
    • calculate(int a, int b):保存された演算を実行し、結果を返す

    必要なヘッダー(<functional>)を含め、MathOperations ヘッダーも必ずインクルードしてください。

  • main.cpp:3つの入力を読み取ります。
    1. 演算名:addsubtractmultiply、または square
    2. 1番目の数値(整数)
    3. 2番目の数値(整数)

    Calculator を作成し、演算名に基づいて設定します。

    • addsubtractmultiply の場合:対応する関数を直接電卓に割り当てます
    • square の場合:std::bind を使用して、exponent に常に 2、multiplier に 1 を使用する power の特殊化版を作成します。バインドされた関数は2つの引数を受け取り、1番目の引数だけを base として使用します(2番目の引数はプレースホルダーを使用して無視できます)。

    演算を設定したら、2つの数値を使用して calculate() を呼び出し、次を出力します。

    Result: [value]

    次に、a + b + 100 を返す lambda を電卓に割り当て、同じ入力で実行して演算を交換できることを示し、次を出力します。

    With bonus: [value]

たとえば、入力が add105 の場合:

Result: 15
With bonus: 115

入力が multiply73 の場合:

Result: 21
With bonus: 110

入力が square40 の場合:

Result: 16
With bonus: 104

このチャレンジでは、std::function によって、異なる呼び出し可能な型(通常の関数、バインドされた関数、lambda)を統一的な方法で保存できることを示します。一方、std::bind を使用すると、一部の引数を固定して既存の関数を適応させることができます。

自分で試してみよう

#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"

using namespace std;

int main() {
    // 入力を読み取る
    string operation;
    int num1, num2;
    cin >> operation >> num1 >> num2;
    
    // Calculator のインスタンスを作成する
    Calculator calc;
    
    // TODO: 操作名に基づいて電卓を設定する
    // "add"、"subtract"、"multiply" の場合: 対応する関数を直接代入する
    // "square" の場合: std::bind を使用して power の特殊版を作成する
    //   常に指数 2 と乗数 1 を使用するもの
    //   ヒント: 基数の引数には std::placeholders::_1 を使用する
    
    if (operation == "add") {
        // ここにコードを書く
    } else if (operation == "subtract") {
        // ここにコードを書く
    } else if (operation == "multiply") {
        // ここにコードを書く
    } else if (operation == "square") {
        // ここにコードを書く - power 関数で std::bind を使用する
    }
    
    // TODO: calculate() を呼び出して結果を出力する
    // 形式: "Result: [value]"
    
    // TODO: 操作の入れ替えを実演する
    // a + b + 100 を返すラムダを電卓に割り当てる
    // 再度 calculate() を呼び出して結果を出力する
    // 形式: "With bonus: [value]"
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C++オンラインコンパイラ