std::functionとstd::bind
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 86/104。
ラムダ式は強力ですが、異なる型の呼び出し可能オブジェクトを統一的な方法で保持したり、既存の関数を必要なシグネチャに適合させたりする必要がある場合があります。<functional> ヘッダーの std::function と std::bind は、これらの問題を解決します。
std::function は、特定のシグネチャに一致するあらゆる呼び出し可能オブジェクト(関数、ラムダ式、関数オブジェクトなど)を保持できる、型消去されたラッパーです:
#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 は、既存の関数の引数の一部を固定することで、新しい呼び出し可能オブジェクトを作成します。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"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1);
swapped("Bob", "Hi"); // Hi, Bob!
}これらのツールは、コールバックをクラスメンバとして保存したり、ストラテジーパターンを実装したりするために、OOPにおいて特に有用です。しかし、モダンな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)を返します。累乗の計算には単純なループを使用してください(指数は非負であると仮定します)。
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を使用して、常に指数 2 と倍率 1 を使用するpowerの特化版を作成します。バインドされた関数は2つの引数を受け取り、最初の引数のみを底(base)として使用するようにします(2番目の引数はプレースホルダーを使用して無視できます)。
操作を設定した後、2つの数値で
calculate()を呼び出し、次のように出力します:Result: [value]次に、
a + b + 100を返すラムダ式を計算機に割り当てて操作の入れ替えを実演し、同じ入力で実行して次のように出力します: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 が異なる呼び出し可能型(通常の関数、バインドされた関数、ラムダ式)を保存する統一された方法を提供し、std::bind が引数の一部を固定することで既存の関数を適合させる方法を示しています。
チートシート
<functional> ヘッダーは、呼び出し可能オブジェクトを柔軟に扱うための std::function と std::bind を提供します。
std::function は、特定のシグネチャに一致する任意の呼び出し可能オブジェクトを格納できる、型消去されたラッパーです。
#include <functional>
std::function<int(int, int)> operation;
operation = add; // 通常の関数
operation = [](int a, int b) { return a * b; }; // ラムダ式std::bind は、既存の関数の引数の一部を固定することで、新しい呼び出し可能オブジェクトを作成します。可変引数には std::placeholders::_1、_2 などを使用します。
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1); // 最初の引数を固定
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1); // 引数の順序を入れ替え
swapped("Bob", "Hi"); // Hi, Bob!これらのツールは、コールバックをクラスメンバとして保存したり、柔軟なコールバックシステムを実装したりするのに便利です。モダンな C++ では、可読性を高めるために 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