2023年12月25日月曜日

cppのクラスメンバ関数をbindを通してstd::functionとして扱う


背景

以前クラスメンバ関数をコールバック関数として渡したいとgithubで要望したところbindを使えば可能と助言をいただきました。

Request of setting notify_callback as class instance. #342

この手法は時々使いたくなる場面があって活用しているものの、その度に上記のissueを開き直したり既存の実装を調べ直したり書き方を検索したりして書き方を思い出しているので、記事としてまとめて見返しやすくします。

bindでクラスメンバ関数を括る

bindを利用して下記のようにまとめれます。
std::bind(対象のクラス::クラスメンバ変数, クラスのインスタンス);

引数がある場合は、placeholdersを引数の数分bindに渡します。
下記の例は引数が2個ある場合のbindです。
std::bind(対象のクラス::クラスメンバ変数のポインタ, クラスのインスタンスのポインタ, std::placeholders::_1, std::placeholders::_2);

具体例

具体例も共有します。
#include <stdlib.h>

#include <functional>
#include <iostream>

void runPoyo(std::function<void()> callbackPoyo) { callbackPoyo(); }
void runNani(std::function<void(int, int)> callbackNani) { callbackNani(0, 1); }

class Hoge {
public:
void poyo() { printf("poyo\n"); }
void nani(int x, int y) { printf("nani %d %d\n", x, y); }
void run() {
std::function<void(void)> callbackPoyo = std::bind(&Hoge::poyo, this);
std::function<void(int, int)> callbackNani = std::bind(
&Hoge::nani, this, std::placeholders::_1, std::placeholders::_2);
runPoyo(callbackPoyo);
runNani(callbackNani);
}
};

int main(int argc, char** argv) {
Hoge instanceHoge;
instanceHoge.run();
std::function<void(void)> callbackPoyo =
std::bind(&Hoge::poyo, &instanceHoge);
std::function<void(int, int)> callbackNani = std::bind(
&Hoge::nani, &instanceHoge, std::placeholders::_1, std::placeholders::_2);
callbackPoyo();
callbackNani(2, 3);
std::function<void(int, int)> callbackNaniChangeValueOrder = std::bind(
&Hoge::nani, &instanceHoge, std::placeholders::_2, std::placeholders::_1);
callbackNaniChangeValueOrder(2, 3);
std::function<void(int)> callbackNaniAssignedVal55 =
std::bind(&Hoge::nani, &instanceHoge, 55, std::placeholders::_1);
callbackNaniAssignedVal55(4); // result: nani 55 4
std::function<void(int)> callbackNaniAssignedVal66 =
std::bind(&Hoge::nani, &instanceHoge, std::placeholders::_1, 66);
callbackNaniAssignedVal66(5); // result: nani 5 66
return 0;
}

上記のプログラムをg++でビルドして実行すると下記の実行結果が出ます。
poyo
nani 0 1
poyo
nani 2 3
nani 3 2
nani 55 4
nani 5 66

Hogeクラスのpoyoをbindでまとめる場合はこう書きます。
  std::function<void(void)> callbackPoyo =
std::bind(&Hoge::poyo, &instanceHoge);
callbackPoyo();

クラスメンバ関数の中でまとめる場合はインスタンスにthisを渡せば良いです。
class Hoge {
public:
void poyo() { printf("poyo\n"); }
void run() {
std::function<void(void)> callbackPoyo = std::bind(&Hoge::poyo, this);
runPoyo(callbackPoyo);
}
};

引数が必要なHogeクラスのnaniをbindでまとめる場合はこう書きます。
  std::function<void(int, int)> callbackNani = std::bind(
&Hoge::nani, &instanceHoge, std::placeholders::_1, std::placeholders::_2);

引数がある関数は、bind時にplaceholderを渡さず値の代入が可能です。
  std::function<void(int)> callbackNaniAssignedVal55 =
std::bind(&Hoge::nani, &instanceHoge, 55, std::placeholders::_1);
callbackNaniAssignedVal55(4); // result: nani 55 4
  std::function<void(int)> callbackNaniAssignedVal66 =
std::bind(&Hoge::nani, &instanceHoge, std::placeholders::_1, 66);
callbackNaniAssignedVal66(5); // result: nani 5 66

おわり

bindを利用するとクラスメンバ関数をstd::function型に変換できるので、std::function型のコールバックを受け取る関数に渡せて便利です。
この記事によって、書きたくなったときの調べ直す時間が短くなるのを願います。

参考

Request of setting notify_callback as class instance. #342
std::bind cppreference

0 件のコメント :