MENU

【C++】コールバックの概要と実装サンプルを紹介

この記事では、C++におけるコールバック処理の概要と、基本的な使い方について紹介します。

C++の環境構築がまだの方は、以下の記事をご覧ください。

目次

コールバックとは

C++におけるコールバックは、関数ポインタや関数オブジェクトを使って、関数の実行を他の関数に委譲する手法です。「関数の実行を他の関数に委譲する」と言うと少し分かりにくいですが、ある関数(呼び出し元)が他の関数(コールバック)に対して、その関数(コールバック)を実行する役割を任せるということです。

コールバックを使うメリット

柔軟な処理の実行

コールバックを使うことで、特定のタイミングやイベントが発生したときに実行する処理を動的に変更できます。これにより、プログラムの動作を柔軟に制御することができます。

コードの再利用性の向上

コールバックを使うことで、汎用的な関数を作成し、その関数内で行う具体的な処理を外部から渡すことができます。これにより、同じコードを異なる文脈で再利用しやすくなります。

モジュール性の向上

コールバックを利用することで、異なるモジュール間の依存関係を減らし、コードを分離しやすくなります。これにより、コードのメンテナンスやテストが容易になります。

イベント駆動型プログラミングの実現

コールバックはイベント駆動型プログラミングの基本的な構成要素です。例えば、GUIプログラムやサーバーサイドプログラムでは、ユーザーの操作や特定のイベントに応じて処理を実行する必要があります。コールバックを使うことで、これらのイベントハンドリングを実現できます。

非同期処理のサポート

非同期処理の完了を待つ間に他の処理を実行するためにコールバックが使用されます。非同期処理が完了したときにコールバック関数を呼び出すことで、効率的な並行処理が可能になります。

コールバックは、イベント駆動型のプログラミング非同期処理などでよく使われます。

サンプルコード

コールバックの代表的な使い方を、以下の4つのパターンで紹介します。

関数ポインタを使ったコールバック

関数ポインタを使うのが最も基本的な方法です。

#include <iostream>

// コールバック関数の型を定義
typedef void (*CallbackFunction)();

void registerCallback(CallbackFunction callback) {
    callback();
}

// コールバックとして登録される関数
void myCallback() {
    std::cout << "Callback called!" << std::endl;
}

int main() {
    // コールバックを登録
    registerCallback(myCallback);
    return 0;
}
typedef void (*CallbackFunction)();

CallbackFunctionという名前で関数ポインタ型を定義しています。この型は、引数がなく、戻り値がvoidである関数へのポインタを指します。

void registerCallback(CallbackFunction callback) {
    callback();
}

registerCallback関数は、CallbackFunction型の引数callbackを受け取ります。この関数は、受け取ったコールバック関数を呼び出します。

void myCallback() {
    std::cout << "Callback called!" << std::endl;
}

myCallback関数は、コールバック関数として使用される関数です。この関数は、標準出力に「Callback called!」と出力します。

int main() {
    // コールバックを登録
    registerCallback(myCallback);
    return 0;
}

registerCallback(myCallback)によって、myCallback関数をコールバックとしてregisterCallback関数に渡しています。registerCallback関数内で、渡されたmyCallback関数が呼び出され、結果として「Callback called!」というメッセージが標準出力に表示されます。

関数オブジェクト(ファンクター)を使ったコールバック

関数オブジェクト(ファンクター)もコールバックとして利用できます。

#include <iostream>

class MyCallback {
public:
    void operator()() {
        std::cout << "Callback called!" << std::endl;
    }
};

template <typename Callback>
void registerCallback(Callback callback) {
    callback();
}

int main() {
    MyCallback myCallback;
    // コールバックを登録
    registerCallback(myCallback);
    return 0;
}
class MyCallback {
public:
    void operator()() {
        std::cout << "Callback called!" << std::endl;
    }
};

MyCallbackクラスは、関数オブジェクト(ファンクター)として機能します。operator()メンバ関数を定義することで、MyCallbackクラスのインスタンスを関数のように呼び出すことができます。

template <typename Callback>
void registerCallback(Callback callback) {
    callback();
}

registerCallbackはテンプレート関数です。任意の型Callbackを受け取ります。引数として渡されたcallbackを実行します。

int main() {
    MyCallback myCallback;
    // コールバックを登録
    registerCallback(myCallback);
    return 0;
}

main関数では、MyCallbackクラスのインスタンスmyCallbackを作成し、registerCallback関数にmyCallbackを渡します。

registerCallback関数内で、渡されたmyCallbackオブジェクトが関数のように呼び出され、結果として「Callback called!」というメッセージが標準出力に表示されます。

少し複雑なのでポイントを以下にまとめます。

関数オブジェクト(ファンクター)

MyCallbackクラスは、operator()を定義することで関数オブジェクトとして機能します。これにより、インスタンスを関数のように呼び出すことができます。

テンプレート関数

registerCallback関数はテンプレート関数として定義されており、任意の型を受け取ることができます。これにより、関数オブジェクトや関数ポインタなど、さまざまな型のコールバックを受け取ることができます。

テンプレート関数が何かわからない方は以下の記事をご覧ください。

std::functionとラムダ式を使ったコールバック

C++11以降では、std::functionとラムダ式を使うと柔軟で強力なコールバック機能が得られます。

#include <iostream>
#include <functional>

// std::functionを使ってコールバックの型を定義
typedef std::function<void()> CallbackFunction;

void registerCallback(CallbackFunction callback) {
    callback();
}

int main() {
    // ラムダ式をコールバックとして登録
    registerCallback([]() {
        std::cout << "Callback called!" << std::endl;
    });

    // 通常の関数をコールバックとして登録
    auto myCallback = []() {
        std::cout << "Another callback called!" << std::endl;
    };
    registerCallback(myCallback);

    return 0;
}
typedef std::function<void()> CallbackFunction;

std::functionを使って、引数を取らず戻り値がvoidである関数型をCallbackFunctionという型名で定義します。std::functionは、様々な種類の関数や関数オブジェクトをラップするための汎用的なクラステンプレートです。

void registerCallback(CallbackFunction callback) {
    callback();
}

registerCallback関数は、CallbackFunction型の引数callbackを受け取り、受け取ったコールバック関数を実行するために、callback()を呼び出します。

// ラムダ式をコールバックとして登録
registerCallback([]() {
    std::cout << "Callback called!" << std::endl;
});

registerCallbackに対して、無名のラムダ式を直接渡しています。このラムダ式は引数を取らず、単に「Callback called!」というメッセージを標準出力に出力します。

// 通常の関数をコールバックとして登録
auto myCallback = []() {
    std::cout << "Another callback called!" << std::endl;
};
registerCallback(myCallback);

auto myCallback = []() { ... };を使って、myCallbackという名前のラムダ式を定義し、それをregisterCallbackに渡しています。このラムダ式は「Another callback called!」というメッセージを標準出力に出力します。

このコードのポイントは以下です。

柔軟性

std::functionを使うことで、ラムダ式や通常の関数を同じように扱えます。これにより、コールバックの実装がより柔軟になります。

型安全性

std::functionは型安全なラッパーであり、コーディングのミスがあればコンパイル時に型の不一致エラーが検出できます。

コンパクトさと可読性の向上

ラムダ式はその場で関数を定義し、それを直接使用することができます。これにより、コードがよりコンパクトになります。コールバックの定義と使用が同じ場所に集約されるため、コードの理解やメンテナンスが容易になります。

メンバ関数をコールバックとして使用

最後はstd::bindを使ってクラスのメンバ関数をコールバックとして使う方法です。

#include <iostream>
#include <functional>

class MyClass {
public:
    void myMemberCallback() {
        std::cout << "Member callback called!" << std::endl;
    }
};

typedef std::function<void()> CallbackFunction;

void registerCallback(CallbackFunction callback) {
    callback();
}

int main() {
    MyClass obj;
    // メンバ関数をコールバックとして登録
    registerCallback(std::bind(&MyClass::myMemberCallback, &obj));
    return 0;
}
class MyClass {
public:
    void myMemberCallback() {
        std::cout << "Member callback called!" << std::endl;
    }
};

MyClassクラスに、myMemberCallbackというメンバ関数を持たせています。

int main() {
    MyClass obj;
    // メンバ関数をコールバックとして登録
    registerCallback(std::bind(&MyClass::myMemberCallback, &obj));
    return 0;
}

&MyClass::myMemberCallbackは、MyClassクラスのmyMemberCallbackメンバ関数へのポインタを表します。

std::bindを使って、&MyClass::myMemberCallback&objにバインドしています。これにより、objのインスタンスでmyMemberCallbackが呼び出されるようにしています。

このコードのポイントは以下です。

メンバ関数をコールバックに使える

std::bindを使うことで、メンバ関数をコールバックとして登録できます。通常の関数ポインタやラムダ式ではクラスのインスタンスを指定する方法が直接的ではないため、このような場面で便利です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

20代の組み込みソフトウェアエンジニア
主な使用言語はC++

---------------------資格---------------------
応用情報技術者
ネットワークスペシャリスト

目次