C
C++

Cの関数ポインタを取るところにC++のラムダ式を使いたい

概要

共有ライブラリやフレームワークなどの関数で、コールバック関数を引数に渡すものがあります。
呼ぶ場所によって処理を変えたい場合、コールバック関数を変えれば良いのですが、その個数が多いときはC++のラムダ式が使えれば便利です。
共通で使えるラムダ式を実行するだけのコールバック関数を用意すれば、ラムダ式で呼ぶところによって処理を変えられるようになります。

コード例

#include <iostream>
#include <thread>
#include <chrono>
#include <functional>

// 直接ラムダ式を引数に取る関数
void doSomething(std::function<void(int error)> completion) {
    std::cout << "doSomething" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    completion(0);
}

// 関数ポインタを引数に取る関数
void doSomething2(void (*completion)(int error, void* userContext), void* userContext) {
    std::cout << "doSomething2" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    completion(0, userContext);
}

// ラムダ式を実行するためのコールバック関数
void callLambda(int error, void* userContext) {
    auto completion = static_cast<std::function<void(int)>*>(userContext);
    (*completion)(error);
    delete completion;
}

int main(int argc, const char * argv[]) {
    // 直接ラムダ式でコールバック処理を指定する
    doSomething([](int error) {
        std::cout << "Completed, errorCode=" << error << std::endl;
    });

    // コールバック関数で間接的にラムダ式を実行する
    doSomething2(callLambda, new std::function<void(int)>([](int error) {
        std::cout << "Completed, errorCode=" << error << std::endl;
    }));

    return 0;
}

まとめ

面倒ですが、C++のメソッドを呼びたいときや、Objective-Cのメソッドを呼ぶときにも使えます。
同じプログラム内であれば、直接ラムダ式を引数に取った方が手軽ですね。

共有ライブラリのときも、直接引数にラムダ式を取ることも可能ですが、共有ライブラリとライブラリ利用側の開発環境のバージョンが異なっていると、安全ではなくなるので、関数ポインタを使った間接的な呼び出しにしておいた方が安全です。

callLambda()の中で関数オブジェクトを解放してしまうので、プログレス処理のコールバックのようなところでは、解放するタイミングと解放しないタイミングを伝える事が出来るようなコードにしておく必要があります。