C・C++の関数ポインタとコールバック
このレッスンでは、Cの関数ポインタとC++のラムダを使って、関数を別の関数に引数として渡します。
はじめに
コールバック(callback)は、関数を別の関数に渡し、必要なときに呼び出してもらう手法です。これにより、汎用的なアルゴリズムが内部の実装の詳細を知らなくても、呼び出す側が選んだ処理を実行できます。たとえば同じ関数applyで、2乗したり、2倍したり、どんな変換でもできます。
大切なポイント
- ポインタは、まだ存在している正しい型のオブジェクトを指すか、nullにしておきましょう。
- 確保したメモリ領域は、ちょうど1回だけ解放しましょう。
- どの関数がメモリの持ち主(ownership)で、どのデータが読み取り専用(const)かをはっきりさせましょう。
CとC++のコード例
C
main.c
#include <stdio.h>
int square(int value) { return value * value; }
int apply(int value, int (*operation)(int)) { return operation(value); }
int main(void) {
printf("%d\n", apply(6, square));
return 0;
}
36
C++
main.cpp
#include <functional>
#include <iostream>
int apply(int value, const std::function<int(int)>& operation) {
return operation(value);
}
int main() {
int factor = 6;
std::cout << apply(factor, [](int value) { return value * value; }) << '\n';
}
36
CとC++の比較
Cの引数int (*operation)(int)は関数ポインタで、int型の引数を1つ受け取りintを返す関数を受け付けるので、apply(6, square)は36を返します。C++のラムダとstd::functionは、より多くの種類の呼び出し可能なオブジェクト(callable)を保存できます。性能が重要なコードでは、std::functionよりテンプレートのほうが適していることが多いです。
C
メモリの持ち主と解放は、すべてプログラマーが自分で保証します。
C++
コンテナとRAIIが、オブジェクトの寿命が終わるときにリソースを自動で解放します。
発展問題
両方のバージョンを実行し、コードを書き換えて、それぞれの言語がもたらす安全性の違いを観察しましょう。
- nullポインタやメモリ確保の失敗で試しましょう。
- AddressSanitizerとUndefinedBehaviorSanitizerを付けて実行し、メモリのエラーを見つけましょう。
- どのポインタがメモリの持ち主で、どのポインタが読むだけかをはっきり書き添えましょう。
チャレンジで練習する
このレッスンの内容を使って実際の問題を解きましょう。各チャレンジはブラウザで動き、すぐにテストされ、AI のレビューも受けられます。
すべてのチャレンジ