読者です 読者をやめる 読者になる 読者になる

ムラサメ研究ブログ

主にゲームやプログラミングのログ

boost::bind (std::bind)を理解する

C++

はじめに

C++で仕事をしていますが、C++って人によって理解度が違います 今回の案件はBoost.Asioで作れという指定なので、どうしてもC++色が強くなります

stream、string、functional、bind、rambda はたまた triboolやtuple、各種コンテナと

他の作業員がC++読めないので 事細かに説明していますが その中でも 最も説明が難しいものの1つが bind等の functionalでしょうか

ってことで、他の作業員への説明資料作成の下書きに ブログにはりつける

bindって?

大雑把にいえば 指定された関数から std::function を作るもの ですがよくわかりませんね 色々便利な関数ポインタ(実際は関数オブジェクトだが)を作るものということです

std::bind と boost::bindがありますが、似たようなものです 元々boost::bindがあり、とても有用なため標準ライブラリ入りしました 大きく違うのは、引数が stlだと std::placeholders::1 と長いですが boostだと 1 と、グローバルネームスペースにあり短いです

大きく2つのメリットを書いておきます

引数を束縛する

bindの名前の由来です

例えば int 2個の引数からなる関数の 第一引数を 2で固定するとか、引数の順番を変えたりとかできます

void hoge( int a, int b ){ std::cout << "arg1=" << a << "   arg2=" << b << "\n" }

auto fn1=boost::bind(hoge, 2, _2); // 第一引数を 2に束縛
auto fn2=boost::bind(hoge, _2, _1); // 引数の順番を逆に!

fn1(5);  // arg1=2  arg2=5  と出力
fn2(1,2);  // arg1=2  arg2=1  と出力

関数型言語のカリー化風なことができましたね

クラスのメンバ関数のポインタを グローバルスペースで使える

何を言っているかわかりませんね クラスのメンバ関数のプロトタイプには、クラススコープが付きます

例えば

struct C {
    void hoge() {}
};

この hogeのプロトタイプは void()() ではなく、void(C::)() です。

なので 下記

struct C {
    void hoge(int n) {}
};
struct C2 {
    void hoge(int n) {}
};

void(*fn)(int) = C::hoge; // Error
void(C::*fn2)(int) = C::hoge; // OK
void(C::*fn3)(int) = C2::hoge; // NG

クラスが固定ならば、上記の クラススコープ入りの関数ポインタで問題ないけど 例えばコールバック等で汎用的なインタフェース作ると困りますね そんな時に bindを使えば、引数が同じであれば クラススコープが異なっていても 同等に扱うことができます ただし、thisポインタが誰であるかを知る必要があるので 第二引数にそのクラスのthisポインタが必要です

struct C {
    void hoge(int n) {}
};
struct C2 {
    void hoge(int n)  {}
};

void hoge(int n){}

C c;
C2 c2;

std::function<void(int)> fn1,fn2,fn3;   // 全部同じ型
fn1 = boost::bind(hoge, _1); // OK  グローバルのhoge関数
fn2 = boost::bind(&C::hoge, c, _1); // OK
fn3 = boost::bind(&C2::hoge), c2, 5); // OK  当然引数も束縛出来る

std::function は、関数オブジェクトのラッパーである。関数オブジェクトは 大雑把にいうと関数ポインタの高機能版である

とりあえず これにより、異なるクラススコープのオブジェクトであっても、引数が同じであれば同じに扱うことが出来る

bindを使わない実装例

例えば Threadの作成などで、グローバル関数から クラスのメンバを呼ぶことがある この時 thisポインタが不明になるので色々と工夫をする必要がある global変数にthisを入れると、オブジェクト1つしか扱えないので却下。では リストで管理する? 面倒で不具合のもと ということで よく、staticのコールバック関数に void*型の引数を作り thisポインタを渡し Castして使う

struct Hoge{
  static void callback(void *self, int n){ static_cast<Hoge*>(self)->hoge(n); };
  void hoge(int n){};
};

setCallback( void(*fn)(void*,int), void* self){
  *fn(self, 5);
}

Hoge h;
setCallback( Hoge::callback, &h );

なんとなくダサいと思ったらその感覚は正しい 上記は function、bindでは こう書ける

struct Hoge{
  void callback(int n){ hoge(n); };
  void hoge(int n){};
};

setCallback( std::function<void(int)> fn){
  *fn(5);
}

Hoge h;
setCallback( boost::bind(&Hoge::callback, &h ));

thisポインタをコールバック側で受けてCastする事もなく、static関数を使うこともなく きれいに書ける

関数オブジェクトとは

functionを理解するには必要

要約すると クラスにoperator()をオーバーロードし、あたかも関数のように見えるオブジェクトである

struct FUNC {
    int operator ()(int n) {
        cout << "operator " << n << endl;
        return n;
    };
};

FUNC f;

f(2); // 関数呼び出しっぽく見えるがメンバ変数の operator(int)を呼び出している

この目の錯覚をつかい 人間にはあたかも関数ポインタだと思わせるのが関数オブジェクトである

その関数オブジェクトを使い、bindっぽい事をしてみる

struct C {
    void hoge(int n) {
        cout << "C::hoge  " << n << endl ;
    }

};

template<class T>
struct FUNCOBJ {
    T *self_;
    FUNCOBJ(T* self) : self_(self) {};

    int operator ()(int n) {
        self_->hoge(n);
        return n;
    };
};

C c;

std::function<int(int)> fn1 = FUNCOBJ<C>(&c);

fn1(5);

このように、一度 C::hogeを呼び出す関数オブジェクトを作り、thisを関数オブジェクトのメンバとしてキャプチャすれば 関数ポインタのように関数オブジェクトを扱うことが出来る。

bindは、引数のテンプレートを処理したり、非常に複雑で中身を理解しきれないが大雑把には 関数オブジェクトを作成し メンバ変数をWrapしている

また、C++11では関数オブジェクトを簡単に書くことが出来る ラムダ関数があるので上記は こう書ける

C c;

std::function<int(int)> fn1 = [&c](int n) { return(c.hoge(n)); };

fn1(5);

ちなみに上のコールバックはラムダだと

struct Hoge{
  void callback(int n){ hoge(n); };
  void hoge(int n){};
};

setCallback( std::function<void(int)> fn){
  *fn(5);
}

Hoge h;
setCallback( [&h](int n){ h.callback(n);} );