関数ポインタ

関数のアドレスを保存。動的な関数呼び出し

概要

関数も、メモリのどこかに保存されており、 アドレスを持っています

その関数のアドレスを指すのが関数ポインタです。 これにより、実行時に「どの関数を呼ぶか」を動的に決定できます。

関数ポインタの宣言

基本形式

// 戻り値 (*ポインタ名)(引数) = 関数のアドレス;
int (*funcPtr)(int, int) = add;

例:加算関数のポインタ

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    // 関数ポインタの宣言と初期化
    int (*funcPtr)(int, int) = add;
    
    // 関数ポインタを通じて関数を呼ぶ
    int result = funcPtr(3, 5);
    std::cout << "結果: " << result << std::endl;
    
    return 0;
}

結果: 8

funcPtr(3, 5)add(3, 5)と同じです。 ただし、どの関数を呼ぶかが実行時に決まります。

複数の関数を指す

例:計算機アプリ

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
    int (*operation)(int, int);
    
    char op;
    std::cout << "演算子(+,-,*): ";
    std::cin >> op;
    
    // どの関数を指すかを動的に決定
    if (op == '+') {
        operation = add;
    } else if (op == '-') {
        operation = subtract;
    } else if (op == '*') {
        operation = multiply;
    }
    
    int a = 10, b = 5;
    std::cout << "結果: " << operation(a, b) << std::endl;
    
    return 0;
}

関数ポインタの配列

例:関数の配列

#include <iostream>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

int main() {
    // 関数ポインタの配列
    int (*operations[])(int, int) = {add, sub, mul, div};
    
    for (int i = 0; i < 4; i++) {
        std::cout << "演算 " << i << ": " << operations[i](10, 3) << std::endl;
    }
    
    return 0;
}

演算 0: 13
演算 1: 7
演算 2: 30
演算 3: 3

関数ポインタをパラメータにする

例:高階関数

#include <iostream>

int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

// 関数ポインタをパラメータに受ける
void performOperation(int x, int y, int (*op)(int, int)) {
    std::cout << "結果: " << op(x, y) << std::endl;
}

int main() {
    performOperation(5, 3, add);       // 8
    performOperation(5, 3, multiply);  // 15
    
    return 0;
}

結果: 8
結果: 15

typedef で短縮表記

関数ポインタの型定義が長いので、 typedefで短縮できます。

#include <iostream>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

// 関数ポインタの型を定義
typedef int (*MathOp)(int, int);

void calculate(int x, int y, MathOp op) {
    std::cout << "結果: " << op(x, y) << std::endl;
}

int main() {
    calculate(10, 5, add);
    calculate(10, 5, sub);
    
    return 0;
}

結果: 15
結果: 5

実践例:ソートアルゴリズム

#include <iostream>

// 2つの要素を比較する関数の型
typedef int (*Comparator)(int, int);

// 昇順の比較
int ascend(int a, int b) {
    return a > b;
}

// 降順の比較
int descend(int a, int b) {
    return a < b;
}

// バブルソート(比較関数を指定)
void bubbleSort(int arr[], int size, Comparator compare) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (compare(arr[j], arr[j + 1])) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int numbers[] = {5, 2, 8, 1, 9};
    int size = 5;
    
    // 昇順にソート
    bubbleSort(numbers, size, ascend);
    std::cout << "昇順: ";
    for (int x : numbers) std::cout << x << " ";
    std::cout << std::endl;
    
    // 降順にソート
    bubbleSort(numbers, size, descend);
    std::cout << "降順: ";
    for (int x : numbers) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}

昇順: 1 2 5 8 9
降順: 9 8 5 2 1

Modern C++:std::function

C++11以降、std::functionを使って、 より柔軟に関数型を扱えます。

#include <iostream>
#include <functional>

int add(int a, int b) { return a + b; }

int main() {
    // std::function を使用
    std::function<int(int, int)> op = add;
    
    std::cout << "結果: " << op(5, 3) << std::endl;
    
    // ラムダ関数も指定可能
    op = [](int x, int y) { return x * y; };
    std::cout << "結果: " << op(5, 3) << std::endl;
    
    return 0;
}

結果: 8
結果: 15

std::functionはより扱いやすく、 modern C++での推奨方法です。

ポイント

  • 関数ポインタで関数のアドレスを保存
  • 実行時にどの関数を呼ぶかを動的に決定
  • typedefで型定義を簡潔に
  • 関数ポインタの配列で複数関数を管理
  • Modern C++ではstd::functionが推奨

よくある誤り

誤り1: 関数ポインタと関数呼び出しの混同

int add(int a, int b) { return a + b; }

int (*funcPtr)(int, int) = add;  // OK
int (*funcPtr)(int, int) = add(); // エラー!: 関数を実行してしまう

誤り2: シグニチャが異なる関数を割り当て

int add(int a, int b) { return a + b; }
double divide(double a, double b) { return a / b; }

int (*funcPtr)(int, int) = add;      // OK
int (*funcPtr)(int, int) = divide;   // エラー!: シグニチャが異なる

誤り3: 宣言時に波括弧をつけ忘れ

int* funcPtr(int, int);  // これは関数の宣言(ポインタではない)
int (*funcPtr)(int, int);  // 妃れが関数ポインタ

やってみよう

練習1: 異なる計算関数をポインタで切り替える計算機。

練習2: 関数ポインタの配列でコールバック処理。

練習3: 比較関数をパラメータにしたソートプログラム。

チャレンジ: std::function を使ったイベントハンドラー。

まとめ

  • 関数ポインタで関数を動的に呼び出す
  • 高階関数の実装が可能
  • typedefで簡潔に表記
  • Modern C++ではstd::functionの使用を検討