SSブログ

関数ポインタを辞めてstd::functionを使おう ついでにコールバックハンドラも [C++11]

「引数を取ってなんか処理して値を返す」的なものをなんでも格納できるstd::functionです。

#include <iostream>
#include <functional>

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

int main()
{
    // intを2つ受け取ってintを返す何かを格納するモノ
    std::function<int(int,int)> f;

    // フリー関数を格納
    f = func;

    // 呼んでみる -> フリー関数が呼ばれる
    std::cout << f(1, 2) << std::endl;

    return 0;
}


実行結果:
3


ここまではたいして面白く無いですが、クラスのメンバ関数を格納できるとしたらどうでしょう?

#include <iostream>
#include <functional>

class C {
    private v_;
public:
    explicit C(int v) : v_(v) {}

    int func(int a, int b)
    {
        return a + b + v_;
    }
};

int main()
{
    // intを2つ受け取ってintを返す何かを格納するモノ
    std::function<int(int,int)> f;

    C obj(3);

    // クラスCのインスタンスobjのメンバ関数を格納
    f = std::bind(&C::func, std::ref(obj), std::placeholders::_1, std::placeholders::_2);

    // 呼んでみる ※objの生存期間に注意
    std::cout << f(1, 2) << std::endl;

    return 0;
}


実行結果:
5


注意は二点。

1. メンバ関数ポインタ(&C::func)に、インスタンスの参照(std::ref(obj))をバインドする
2. 引数分のプレースホルダ(std::placeholders::_1, std::placeholders::_2)をバインドする

1.は、メンバ関数の実行にはそのオブジェクトのインスタンスが必要ということ。
なので、呼び出し時にバインドしたインスタンスが存在しなければなりません。

2.は関数プログラミングで言うところの部分適用です。
バインドせずに後から指定する引数はこの書き方で明示が必要です。

応用として、コールバック用の仮想オブジェクトを用意していたところを、
std::functionを受け取るようにしてみます。

◯ふつうのコールバック
#include <iostream>
#include <functional>

// コールバックハンドラ用のインタフェースクラス
class IX {
public:
    virtual void callback(int a, int b) = 0;
};

// コールバックされる側の実装 -> IXを実装する
class X : public IX {
public:
    virtual void callback(int a, int b) {
        std::cout << "callback: " << (a+b) << std::endl;
    }
};

// コールバック元
void cb(IX* hdl)
{
    hdl->callback(1,2);
}

int main()
{
    X o;
    cb(&o);

    return 0;
}


コールバック用のクラスを作ったり、実装したり、ポインタ渡しをしたり、
いろいろと面倒ですね。
一つのクラスで複数のコールバックを受けようとするとさらに面倒です。
(するのか?という疑問は別として)

◯std::functionを使って同じことをしてみる
class Y {
public:
    void callback(int a, int b) {
        std::cout << "callback: " << (a+b) << std::endl;
    }
};

void cbf(std::function<void(int,int)> f)
{
    f(1,2);
}

int main()
{
    Y o;
    cbf(std::bind(&Y::callback, std::ref(o), std::placeholders::_1, std::placeholders::_2));

    return 0;
}


またコールバックハンドラがクラスのメンバという必要もありません。

◯ラムダ関数にコールバックさせる
void cbf(std::function<void(int,int)> f)
{
    f(1,2);
}

int main()
{
    cbf( [] (int a, int b) { std::cout << "lambda callback " << (a+b) << std::endl; });
}


「std::functionでコールバック」はコールバックさせるハンドラの実装に柔軟性を
持たせられる利点があります。
がしかし、どの関数がコールバックされるものかが、わかりにくくなるという
デメリットがあるかもしれません。

その点、「普通の〜」はclass Xが'コールバックを実装している'ということを明示しているとも
言えるので、どちらを選ぶかは場面によって検討したほうが良いかも。

(柔軟性とわかりやすさは裏表?)

nice!(0)  コメント(0)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。