C++11のstd::function,std::bind,std::placeholders
7687 ワード
会社の言うプロジェクトコードでstd::function、std::bind、std::placeholdersの3つのC++11の特性を見て、理解してから、やはり役に立つことに気づいたので、ここに記録しておきましょう.この3つの特性は一般的に一緒に使われているようなので、一緒に話しました.
3つのプロパティはfunctionalファイルで定義されているため、使用するには「#include」が必要です.
1. 基本的な紹介
ここではまず3つの特性を簡単に紹介します.
1.1 std::function
すなわち、汎用的なマルチステート関数カプセルであり、そのインスタンスは、関数、lambda式、bind式、または他の関数オブジェクト、およびメンバー関数ポインタおよびデータ・メンバーポインタを指す任意の呼び出し可能なターゲットを格納、割り当て、および呼び出すことができます. 格納された呼び出し可能オブジェクトはstd::fucntionのターゲットであり、std::functionに値を割り当てることでターゲットを格納すると、std::functionオブジェクトを呼び出すことで格納されたオブジェクトを間接的に呼び出すことができ、一般的に格納されているオブジェクトは関数である.std::functionにターゲットが含まれていない場合は、空のターゲットであり、このようなstd::functionを呼び出すとstd::bad_が投げ出されます.function_コール異常. 実際に格納に使用されるstd::functionもオブジェクトであり、格納時に格納先の戻り値やパラメータなどを指定する必要がある.
1.2 std::bind
std::bindは関数テンプレートであり、関数アダプタのように呼び出し可能なオブジェクトを受け入れ、元のパラメータリストに適合する新しい呼び出し可能なオブジェクトを生成します.このテンプレートはstd::functionオブジェクトを返します.この関数では,パラメータ順序の調整や指定したパラメータを固定値に設定することも可能である.
1.3 std::placeholders
プレースホルダ、functionalファイルを表示すると、c++11には29のプレースホルダがあり、それぞれ_1~_29,一般的にstd::placeholders::_と書く1そうですね.プレースホルダの役割は、パラメータを表すために使用されます.std::placeholders::1はstd::bindで得られたstd::functionオブジェクトが呼び出されたときに渡される最初のパラメータを表し、std::placeholders::2は2番目のパラメータです.
std::placeholders::_を調整します.xはstd::bind時の順序で,パラメータ順序を調整する役割を果たす.また、std::bindのときにstd::placeholders::_を使わなくてもいいです.xは、std::functionが格納しているオブジェクトを呼び出すように固定値として直接書きます.このようにstd::functionが格納しているオブジェクトを呼び出すと、対応する位置のパラメータが固定値になります.
2. 例
次のコードは使用例ですが、コンパイル時に「-std=c++11」を持参しないとコンパイルできません.
また,上記の例3と例7から,クラスの一般メンバーの格納にはstd::bind,1つは使用されず,実際にstd::functionオブジェクトを呼び出す場合も異なる2つの方式があることが分かった.しかし、実際の呼び出しプロセスは同じですが、std::bindはアダプタとして機能し、setIDの最初のパラメータをc 1のアドレスにデフォルト設定しています.bindを使用しない場合は、tClassオブジェクトに転送する必要があります.どうしてこんなことができるの?実際にクラスのメンバー関数を呼び出すとき、コンパイルされたアセンブリコードは、最初のパラメータがクラスオブジェクトのthisポインタであることをデフォルト化し、2番目のパラメータが最初から私たちが入力したパラメータなので、ここでの呼び出し方法は、より下位の呼び出し方法に近いです.
上記の例3,8,9からプレースホルダstd::placeholderの役割が分かる.
3. なぜstd::functionを使うのか
最初にstd::functionの使い方を見たとき、思いついたのは関数ポインタですが、なぜポインタがあったのか、std::functionを作らなければならないのでしょうか.後で資料を探して知ることができて、主に2つの点があります:1. 関数ポインタは安全ではありません.関数ポインタで呼び出されると、パラメータチェックも戻り値もチェックされません.強制型変換によるものでしょうから、このままでは問題になりやすいです.2. 関数ポインタはクラスのメンバー関数にバインドできません.コンパイル時にエラーが表示されます.一番大切なのはやはり第一点でしょう.
上記の例では、関数ポインタに問題があることが容易にわかりますが、std::functionはクラステンプレートであるため、パラメータ伝達時にパラメータや戻り値の検査を行うので、関数ポインタよりずっと安全です.
4. 適用例
std::functionはクラスのメンバー関数にバインドできるため、同じインタフェースを使用して、同じクラスで複数の他のクラスのメンバー関数を同時に呼び出すことができます.同社では,この特性を用いてイベントの登録と処理メカニズムを実現している.コードの一部は次のとおりです.
イベントがトリガーされる場合は、対応するイベントidのvectorを巡回し、std::functionオブジェクトを1つずつ実行するだけでよい.ここでは、すべてのクラスのイベントの実行関数(メンバー関数)を簡単に保存できます.
とても使いやすくて、本当にいい特性です.
3つのプロパティはfunctionalファイルで定義されているため、使用するには「#include」が必要です.
1. 基本的な紹介
ここではまず3つの特性を簡単に紹介します.
1.1 std::function
template< class >
class function; /* undefined */
template< class R, class... Args >
class function;
公式説明は、「Class template std::function is a general-purpos polymorphic function wrapper.Instances of std::function can store,copy,and invoke any Callable target--functions,lambda expressions,bind expressions,or other function objects,as well as pointers to member functions and pointers to data membs.」すなわち、汎用的なマルチステート関数カプセルであり、そのインスタンスは、関数、lambda式、bind式、または他の関数オブジェクト、およびメンバー関数ポインタおよびデータ・メンバーポインタを指す任意の呼び出し可能なターゲットを格納、割り当て、および呼び出すことができます. 格納された呼び出し可能オブジェクトはstd::fucntionのターゲットであり、std::functionに値を割り当てることでターゲットを格納すると、std::functionオブジェクトを呼び出すことで格納されたオブジェクトを間接的に呼び出すことができ、一般的に格納されているオブジェクトは関数である.std::functionにターゲットが含まれていない場合は、空のターゲットであり、このようなstd::functionを呼び出すとstd::bad_が投げ出されます.function_コール異常. 実際に格納に使用されるstd::functionもオブジェクトであり、格納時に格納先の戻り値やパラメータなどを指定する必要がある.
1.2 std::bind
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
公式説明は「The function template bind generates a forwarding call wrapper for f.Calling this wrapper is equivalent to invoking f with some of its arguments bound to args.」std::bindは関数テンプレートであり、関数アダプタのように呼び出し可能なオブジェクトを受け入れ、元のパラメータリストに適合する新しい呼び出し可能なオブジェクトを生成します.このテンプレートはstd::functionオブジェクトを返します.この関数では,パラメータ順序の調整や指定したパラメータを固定値に設定することも可能である.
1.3 std::placeholders
プレースホルダ、functionalファイルを表示すると、c++11には29のプレースホルダがあり、それぞれ_1~_29,一般的にstd::placeholders::_と書く1そうですね.プレースホルダの役割は、パラメータを表すために使用されます.std::placeholders::1はstd::bindで得られたstd::functionオブジェクトが呼び出されたときに渡される最初のパラメータを表し、std::placeholders::2は2番目のパラメータです.
std::placeholders::_を調整します.xはstd::bind時の順序で,パラメータ順序を調整する役割を果たす.また、std::bindのときにstd::placeholders::_を使わなくてもいいです.xは、std::functionが格納しているオブジェクトを呼び出すように固定値として直接書きます.このようにstd::functionが格納しているオブジェクトを呼び出すと、対応する位置のパラメータが固定値になります.
2. 例
次のコードは使用例ですが、コンパイル時に「-std=c++11」を持参しないとコンパイルできません.
#include
#include
int g_Minus(int i, int j)
{
std::cout << "in g_Minus: " << i << " " << j << std::endl;
return i - j;
}
class tClass {
public:
void setID(int id) {
id_ = id;
}
int getID() {
return id_;
}
static int wrongID() {
return 100;
}
int id_ = 5;
private:
void psetID(int id) {
id_ = id;
}
};
struct Add
{
int operator()(int i, int j) {
return i + j;
}
};
template
struct Sub
{
T operator()(T i, T j) {
return i - j;
}
};
template
T Mul(T i, T j) {
return i * j;
}
int main()
{
// 1.
std::function f = g_Minus;
int ret = f(1,2);
std::cout << ret << std::endl;
// 2.
std::function f2 = &tClass::wrongID;
ret = f2();
// 3.
tClass c1;
std::function f3 = std::bind(&tClass::setID, &c1, std::placeholders::_1);
std::cout << c1.getID() << std::endl;
f3(10);
std::cout << c1.getID() << std::endl;
// 4.
std::function f4 = Add(); // ,Add()
std::cout << f4(5, 8) << std::endl;
// 5.
std::function f5 = Sub();
std::cout << f5(11.55, 3.33) << std::endl;
// 6.
std::function f6 = Mul;
std::cout << f6(11.55, 3.33) << std::endl;
// 7.
std::function f9 = &tClass::setID;
f9(c1, 150);
std::cout << c1.getID() << std::endl;
// 8. , 5
std::function f8 = std::bind(g_Minus, std::placeholders::_1, 5);
std::cout << f8(6) << std::endl;
// 9.
std::function f10 = std::bind(g_Minus, std::placeholders::_2, std::placeholders::_1);
std::cout << f10(8, 3) << std::endl;
// 10.
std::function n1 = &tClass::id_;
std::cout << n1(c1) << std::endl;
return 1;
}
以上の説明を組み合わせると、std::functionの使い方は基本的に理解できますが、ここではlambda式の場合を例に挙げていません.私のC++11のlambda式はまだ熟知していないからです.また,上記の例3と例7から,クラスの一般メンバーの格納にはstd::bind,1つは使用されず,実際にstd::functionオブジェクトを呼び出す場合も異なる2つの方式があることが分かった.しかし、実際の呼び出しプロセスは同じですが、std::bindはアダプタとして機能し、setIDの最初のパラメータをc 1のアドレスにデフォルト設定しています.bindを使用しない場合は、tClassオブジェクトに転送する必要があります.どうしてこんなことができるの?実際にクラスのメンバー関数を呼び出すとき、コンパイルされたアセンブリコードは、最初のパラメータがクラスオブジェクトのthisポインタであることをデフォルト化し、2番目のパラメータが最初から私たちが入力したパラメータなので、ここでの呼び出し方法は、より下位の呼び出し方法に近いです.
上記の例3,8,9からプレースホルダstd::placeholderの役割が分かる.
3. なぜstd::functionを使うのか
最初にstd::functionの使い方を見たとき、思いついたのは関数ポインタですが、なぜポインタがあったのか、std::functionを作らなければならないのでしょうか.後で資料を探して知ることができて、主に2つの点があります:1. 関数ポインタは安全ではありません.関数ポインタで呼び出されると、パラメータチェックも戻り値もチェックされません.強制型変換によるものでしょうから、このままでは問題になりやすいです.2. 関数ポインタはクラスのメンバー関数にバインドできません.コンパイル時にエラーが表示されます.一番大切なのはやはり第一点でしょう.
#include
void test(int param)
{
int ret = 0;
for (int i = 0; i < param; ++i) {
ret += i;
}
ret >>= 16;
ret |= (ret << 16);
}
typedef bool (*PFUNC)(char args, int arg2);
int main(void)
{
PFUNC func;
func = (PFUNC)test;
if (func('a', 111)) {
std::cout << "good" << std::endl;
}
else {
std::cout << "bad" << std::endl;
}
return 0;
}
上の関数ではtestは値を返さずに1つのパラメータしか必要としませんが、2つのパラメータが必要でブールタイプの関数ポインタを返すアドレスを強制的に割り当てることができ、問題が発生しやすいです.クラスにバインドできないメンバー関数については、コンパイルが不合格であることをテストしたので、コードもありません.上記の例では、関数ポインタに問題があることが容易にわかりますが、std::functionはクラステンプレートであるため、パラメータ伝達時にパラメータや戻り値の検査を行うので、関数ポインタよりずっと安全です.
4. 適用例
std::functionはクラスのメンバー関数にバインドできるため、同じインタフェースを使用して、同じクラスで複数の他のクラスのメンバー関数を同時に呼び出すことができます.同社では,この特性を用いてイベントの登録と処理メカニズムを実現している.コードの一部は次のとおりです.
typedef std::function CbFuncType;
class EMgr {
public:
void Register(uint32_t event_type, CbFuncType func);
void Raise(Role& role, Event event);
private:
std::map > event_hendle_map_;
};
typedef Singleton EMgrSingleton;
#define REGISTER(event, func) \
EMgrSingleton::get_mutable_instance().Register(event, \
std::bind(&func, this, std::placeholders::_1, std::placeholders::_2));
登録するクラス内の同じイベントの実行関数の宣言はすべて同じです.登録時にstd::bindにより匿名オブジェクトが対応するイベントidにバインドされ、イベントmapに対応するvectorに追加されます.ここでも登録したクラスのオブジェクトを渡す必要があります.上のthisは各クラスでREGISTERマクロを使用すると対応するクラスオブジェクトに置き換えられます.イベントがトリガーされる場合は、対応するイベントidのvectorを巡回し、std::functionオブジェクトを1つずつ実行するだけでよい.ここでは、すべてのクラスのイベントの実行関数(メンバー関数)を簡単に保存できます.
とても使いやすくて、本当にいい特性です.