呼び出し可能オブジェクトの選択
ラムダ、bind、ポインタを比較します
「呼び出し可能オブジェクトの選択」はCoddyKit上の無料C++ Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC++ Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C++ Academyコースには全4レッスンが含まれています。
呼び出し可能オブジェクトのツールボックス
C++には、動作を渡すための方法がいくつかあります。関数ポインタ、ラムダ、ファンクタ、std::bind、std::functionなどです。それぞれ適した用途が異なります。
- 通常は速度と柔軟性のどちらを優先するかがトレードオフになります。
- 読みやすさも重要です。
#include <iostream>
int twice(int x) { return x * 2; }
int main() {
int (*fp)(int) = twice;
auto lam = [](int x) { return x * 2; };
std::cout << fp(5) << ' ' << lam(5) << '\n';
return 0;
}関数ポインタを使用する場合
小さく状態を持たないコールバックが必要で、Cとの互換性またはオーバーヘッドゼロを求める場合は、関数ポインタを使用してください。
#include <iostream>
void logMsg(const char* m) { std::cout << "LOG: " << m << '\n'; }
int main() {
void (*handler)(const char*) = logMsg;
handler("started");
return 0;
}ラムダを使用する場合
ラムダは現代的なデフォルトの選択肢です。簡潔で、状態をキャプチャでき、テンプレートやアルゴリズム内でコンパイラーによって効率よくインライン化されます。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
for (int x : v) std::cout << x << ' ';
std::cout << '\n';
return 0;
}ラムダで状態をキャプチャする
ラムダは周囲の変数をキャプチャできます。これは関数ポインタにはできないため、コンテキストを認識するコールバックに適しています。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int threshold = 3;
std::vector<int> v = {1, 2, 3, 4, 5};
int c = std::count_if(v.begin(), v.end(), [threshold](int x) { return x > threshold; });
std::cout << c << '\n';
return 0;
}ファンクタを使用する場合
ファンクタ(operator()を持つstruct)は、設定を保持でき、多くの場所で使用できる、再利用可能な名前付き操作に適しています。
#include <iostream>
#include <vector>
#include <algorithm>
struct AboveThreshold {
int limit;
bool operator()(int x) const { return x > limit; }
};
int main() {
std::vector<int> v = {1, 5, 2, 8};
std::cout << std::count_if(v.begin(), v.end(), AboveThreshold{4}) << '\n';
return 0;
}ラムダとbindの比較
どちらも引数をあらかじめ設定できます。ラムダのほうが通常は明確で、コンパイラーによる最適化も容易なため、std::bindよりラムダを優先してください。
#include <iostream>
int power(int base, int exp) {
int r = 1;
for (int i = 0; i < exp; ++i) r *= base;
return r;
}
int main() {
auto square = [](int x) { return power(x, 2); };
std::cout << square(6) << '\n';
return 0;
}std::functionを使用する場合
具体的な型が不明な呼び出し可能オブジェクトを格納する必要がある場合は、std::functionを使用してください。たとえば、クラスメンバーやコンテナーに格納する場合です。
#include <iostream>
#include <functional>
#include <vector>
int main() {
std::vector<std::function<int(int)>> pipeline;
pipeline.push_back([](int x) { return x + 1; });
pipeline.push_back([](int x) { return x * 3; });
int v = 2;
for (auto& f : pipeline) v = f(v);
std::cout << v << '\n';
return 0;
}テンプレートでオーバーヘッドを避ける
関数が呼び出し可能オブジェクトを受け取るだけで、格納する必要がない場合は、テンプレートパラメーターを使うと型消去を避けて最大限の速度を維持できます。
#include <iostream>
template <typename F>
int applyTwice(int x, F f) { return f(f(x)); }
int main() {
std::cout << applyTwice(3, [](int n) { return n + 10; }) << '\n';
return 0;
}パフォーマンスの比較
おおまかには、テンプレート化された呼び出し可能オブジェクトまたはキャプチャなしラムダが最も高速で、関数ポインタがそれに近く、std::functionは間接呼び出しとメモリ確保の可能性があるため最も低速です。
#include <iostream>
#include <functional>
int main() {
auto fast = [](int x) { return x + 1; };
std::function<int(int)> flexible = fast;
std::cout << fast(1) << ' ' << flexible(1) << '\n';
return 0;
}選択ガイド
簡単なガイド:
- 任意の呼び出し可能オブジェクトを格納する必要がありますか?
std::functionを使用します。 - ジェネリック関数で1つを受け取るだけですか?テンプレートパラメーターを使用します。
- 状態を持つ動作をインラインで記述しますか?ラムダを使用します。
- C APIとの連携または状態を持たない動作ですか?関数ポインタを使用します。
#include <iostream>
#include <functional>
void runStored(const std::function<void()>& cb) { cb(); }
template <typename F>
void runGeneric(F f) { f(); }
int main() {
runStored([] { std::cout << "stored\n"; });
runGeneric([] { std::cout << "generic\n"; });
return 0;
}すべてを組み合わせる
小さなイベントシステムでは、ハンドラーをジェネリックに受け取り、後から呼び出せるようにstd::functionへ格納できます。
#include <iostream>
#include <functional>
#include <vector>
struct Dispatcher {
std::vector<std::function<void(int)>> handlers;
template <typename F> void on(F f) { handlers.emplace_back(f); }
void fire(int e) { for (auto& h : handlers) h(e); }
};
int main() {
Dispatcher d;
d.on([](int e) { std::cout << "got " << e << '\n'; });
d.fire(7);
return 0;
}理解度チェック
呼び出し可能オブジェクトの選択についての理解度を確認してください。
まとめ
呼び出し可能オブジェクトの選び方を学びました。
- 状態を持たず、Cと互換性のあるコールバックには関数ポインタ
- インラインで状態を持つ動作には、デフォルトとしてラムダ
- 再利用可能な名前付き操作にはファンクタ
- 呼び出し可能オブジェクトを受け取るだけの場合はテンプレートパラメーター(最速)
- 型が不明な呼び出し可能オブジェクトを格納する必要がある場合は
std::function
これで関数オブジェクトとstd::bindのコースは完了です。
よくある質問
「呼び出し可能オブジェクトの選択」レッスンは無料ですか?
はい。「呼び出し可能オブジェクトの選択」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C++ Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C++ Academyコースには全4レッスンが含まれています。
「呼び出し可能オブジェクトの選択」で何を学びますか?
ラムダ、bind、ポインタを比較します ブラウザで直接実行するハンズオンコードでC++ Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C++ Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC++ Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「呼び出し可能オブジェクトの選択」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC++ Academyレッスンでコードを書いて実行できますか?
はい。すべてのC++ Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 関数ポインタ
- std::function
- std::bind
- 呼び出し可能オブジェクトの選択