std::functionとstd::bind
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 86/104。
lambda は強力ですが、異なる型の呼び出し可能オブジェクトを統一的な方法で保存したり、既存の関数を必要なシグネチャに合わせて適応させたりする必要がある場合があります。<functional> ヘッダーの std::function と std::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を使用してください。
チャレンジ
簡単std::function と std::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.h:std::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つの入力を読み取ります。- 演算名:
add、subtract、multiply、またはsquare - 1番目の数値(整数)
- 2番目の数値(整数)
Calculatorを作成し、演算名に基づいて設定します。add、subtract、multiplyの場合:対応する関数を直接電卓に割り当てますsquareの場合:std::bindを使用して、exponent に常に 2、multiplier に 1 を使用するpowerの特殊化版を作成します。バインドされた関数は2つの引数を受け取り、1番目の引数だけを base として使用します(2番目の引数はプレースホルダーを使用して無視できます)。
演算を設定したら、2つの数値を使用して
calculate()を呼び出し、次を出力します。Result: [value]次に、
a + b + 100を返す lambda を電卓に割り当て、同じ入力で実行して演算を交換できることを示し、次を出力します。With bonus: [value]- 演算名:
たとえば、入力が add、10、5 の場合:
Result: 15
With bonus: 115入力が multiply、7、3 の場合:
Result: 21
With bonus: 110入力が square、4、0 の場合:
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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス12現代的なC++の機能
ムーブセマンティクスと右辺値完全転送ラムダ式を深く理解するstd::functionとstd::bindconstexprとconsteval構造化束縛optional、variant、any自分で練習してみよう: C++オンラインコンパイラ