ポインタとメモリ

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 のレビューも受けられます。

すべてのチャレンジ