【C++STL学習と応用まとめ】22:関数の組み合わせの1:std::bind(since C++11)の使い方
このシリーズの文章の目次はここです:目次.カタログを通してSTLの全体的な理解を得ることができます
前言
std::bindはSTLが関数の組合せ概念を実現する重要な手段であり、本文はstd::bindというテンプレート関数の使い方をまとめ、std::bindバインド一般関数(関数ポインタ)、lambda式、メンバー関数、メンバー変数、テンプレート関数、ネストstd::bindなどの文法の詳細と注意の問題を与えた.
以下の用語について説明します. std::placeholders:プレースホルダ、プレースホルダにバインドされたパラメータは、実際の呼び出しで実際のパラメータに置き換えられます. std::decay:劣化タイプ std::forward:転送 Callable:呼び出し可能オブジェクト lambda:functorsの構文糖で、関数として を使用する関数(オブジェクト)を定義します. trailing return types:関数を宣言(定義)するときに、関数の戻り値がパラメータのタイプに依存するために使用されます.あるいは、関数戻りタイプ記述が複雑である場合、タイプ記述 を簡略化するために用いる. reference_wrapper:コンテナにカプセル化したり、リファレンスを渡したりするために、パッケージクラステンプレートを参照します.
std::bindって何?
ネーミング空間接頭辞stdを見れば、bindは標準ライブラリの実の息子であり、
std::bindは関数テンプレートで、関数アダプタのように、もともとN個のパラメータを受信していた関数fnを、いくつかのパラメータをバインドすることで、M個のパラメータを受信した関数retを返すとともに、パラメータの順序調整などの操作を実現することができます.
その原型には2つの形式があり、以下の通りです.
プロトタイプ
bindは、argsにバインドするfnベースの関数オブジェクト(function object)を返す.
fnのパラメータは値にバインドするかplaceholdersにバインドするか(プレースホルダ、例えば_1,_2,...,_N)
パラメータ: fn:1つのCallableオブジェクト(function objects、関数ポインタまたは参照、メンバー関数ポインタまたはメンバー変数ポインタ)であり、そのパラメータはargsによってバインドされる args:可変長パラメータ、または具体的な値、またはプレースホルダ.注意:その長さはfnが受信したパラメータの個数と一致しなければならない 戻り値
bindの戻り値をretにします.
その戻り値retは、タイプTが指定するfunction objectであり、Tは、
retというタイプで、次のメンバーが含まれています.
1.メンバー変数: 1: 2:
retのこの2つのメンバーオブジェクトはbindから伝達された関数とパラメータをそれぞれ保存して将来の呼び出しタスクを実現する.
2.コンストラクション関数: retのすべてのメンバーオブジェクトがコピー可能である場合、それ自体もコピー可能である .と同様に、すべてのメンバーが可動構造である場合、それ自体も可動構造である である.
3.メンバータイプ:result_type Fnが関数ポインタまたはメンバー関数ポインタである場合retのresult_typeはFnの戻り値のタイプである. Fnがクラスであり、内部にresult_が定義されている場合type、じゃあresult_typeはFn::result_に等しいtype. retが第2の形式でbindを呼び出して得られた、すなわち です
4.メンバー関数operator()(これに重点を置いてbindの戻り値retは、関数呼び出しとして使用される)
retはbindの戻り値としてret:ret(a 1,a 2,a 3,...ai)を呼び出すと仮定する.このときret内部に保存されている bindを呼び出すときに retを作成するときにネストされたbind、すなわちret=bind(fn,args...)のパラメータリストargsに、 retを作成する際にプレースホルダplaceholders、すなわちret=bind(fn,arg 1,arg 2,...,_1,_2,...)を用いる場合(_1,_2,..., それ以外の場合、ret内部に保存されているargs、すなわち前述の_Mybargs(bind呼び出し時にバインドされたパラメータたち)は左の値で_に渡されますMyFunは呼び出しを完了するため、これらのパラメータはgと同じcv限定属性を持つ.
g(a 1,a 2,...,ai)では、bindを呼び出すとplaceholderが_1,g(a 1,a 2,a 3)では、a 2,a 3はマッチングされず、マッチングされていないパラメータは評価されるが、破棄される.
gがvolatile(volatile or const volatile)と指定する場合、結果は定義されない.
「あんなにたくさん言ったのに、bを装いたいんだ」とbindの実際のコードでの応用を見てみましょう.
2つの極端な状況からbindプロセスとstd::placeholdersの使用を正しく理解する
bindをより理解しやすく使用するために、fnのパラメータは値にバインドされるかplaceholdersにバインドされるかを強調します.
極端1:値に完全にバインド
極端2:std::placeholdersに完全にバインド
この2つの極端な場合を除いて,bindはほとんどの場合,値とプレースホルダを混合してバインドされる.
bindプロセス分析及び呼び出しパラメータ制御
bindのバインドプロセスを解析するときに、bind呼び出しにエラーがあるかどうか、bind戻り値retを呼び出すときにどのように正しくパラメータを渡すかをどのように決定しますか?たとえば、次のbind呼び出しを行います.
分析プロセス:
1.bind(f,args...)の合法性分析
fに必要なパラメータの個数をNとする、bind(f…)において、与えられた値の個数をVとし、与えるプレースホルダの個数をSとする.
合法的なbind呼び出しの場合、必ずN==V+Sがある.V+SがNを超えているか、N未満である場合、コンパイルはエラーを報告する.
したがって、上記のmix定義では、mix 1、mix 3、mix 4、mix 5のみが合法である.mix 2のパラメータ個数はすでに6個あるが,fは5個しか必要としない.mix 6のパラメータの個数ペアですが、プレースホルダが大きすぎて、VC++(2013)コンパイラ実装で最大20です.
2.bind戻り値retの呼び出し参照書き方
bind(f,args...)で最大のプレースホルダを_とするM.(例えばmix 1,mix 2,mix 3中M=2;mix 4中M=3;mix 5中M=1;mix 6中M=100;
次のようになります.パラメータ個数retの呼び出しには、少なくともMパラメータが与えられる.1~_Mはret(args...)パラメータリストの左から右にかけて下付き順にパラメータをバインドするものであり、M個未満ではエラーが報告され、M個以上では破棄される. パラメータ順序ret(args...)におけるパラメータとplaceholders:1~_Mの対応は簡単で、下付きは1から、順番に対応します.しかし、fにバインドする順序はbind(fn,args...)におけるplaceholdersの順序によって決まる.bind(f,_1,_2,_3,_4,_5)は、ret(args...)のパラメータを左から右の順にfにバインドする.bind(f,_5,_4,_3,_2,_1)は逆の順序でバインドされる.
したがって、上記で定義した正当なmixの呼び出し例および出力は、以下のようにすることができる.
mix 6の中の_に気づくかもしれません100, _50、このような大きなプレースホルダはコンパイルできません.
placeholdersの最大値はVC++で20であり、その最大値は具体的なコンパイラに依存して実現され、この最大値にこだわる必要はない.大きなプレースホルダは一般的には使用されません.プレースホルダを使用すると100、1つしか使っていませんが、これは呼び出し者が少なくとも100のパラメータを提供する必要があることを意味します.あなたは誰を殺したいですか?
次に、bindの呼び出し例をfnの分類に従って示す.
std::bindバインド一般関数、lambda式
std::bindバインドクラスメンバー関数、メンバー変数
メンバー関数が一般的な関数と異なる特殊な点は、最初のパラメータがそのタイプのオブジェクト(またはオブジェクトのポインタまたは参照)である必要があることです.
std::bindバインドテンプレート関数
ネストstd::bind共有std::placeholder.
bindと標準ライブラリの連携
bindとスマートポインタ
last but not least
bindのパラメータはcopyまたはmoveによってターゲット関数に渡され、指定された参照転送が表示されない限り、
また、moveはオブジェクトの状態を変更するため、bindパラメータリストでplaceholdersを再利用する場合は、パラメータがmovedによって削除された場合を考慮して、パラメータが左値または移動できない右値の場合にのみplaceholdersを再利用することを推奨します.
ソースコード標準c++参照のテストコード1 標準c++参照のテストコード2 本明細書のテストコード 作者のレベルは有限で、関连する知识の理解と総括に対してどうしても间违いがあって、また指摘することを望んで、とても感谢します!
githubブログへようこそ、当駅と同期して更新します
前言
std::bindはSTLが関数の組合せ概念を実現する重要な手段であり、本文はstd::bindというテンプレート関数の使い方をまとめ、std::bindバインド一般関数(関数ポインタ)、lambda式、メンバー関数、メンバー変数、テンプレート関数、ネストstd::bindなどの文法の詳細と注意の問題を与えた.
以下の用語について説明します.
std::bindって何?
ネーミング空間接頭辞stdを見れば、bindは標準ライブラリの実の息子であり、
<functional>
に含まれていることがわかる.std::bindは関数テンプレートで、関数アダプタのように、もともとN個のパラメータを受信していた関数fnを、いくつかのパラメータをバインドすることで、M個のパラメータを受信した関数retを返すとともに、パラメータの順序調整などの操作を実現することができます.
その原型には2つの形式があり、以下の通りです.
プロトタイプ
// simple(1)
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
bindは、argsにバインドするfnベースの関数オブジェクト(function object)を返す.
fnのパラメータは値にバインドするかplaceholdersにバインドするか(プレースホルダ、例えば_1,_2,...,_N)
パラメータ:
bindの戻り値をretにします.
auto ret = std::bind(fn, args&&...)
// or
auto ret = std::bind<Ret>(fn, args&&...)
その戻り値retは、タイプTが指定するfunction objectであり、Tは、
std::is_bind_expression<T>::value == true
の条件を満たす.retというタイプで、次のメンバーが含まれています.
1.メンバー変数:
decay<Fn>::type
型のオブジェクト(一応_Myfunという)は、std::forward<Fn>(fn)
で構成されている.簡単に言えば、bindの時に渡されたfnオブジェクトを保存することである.tuple<typename decay<Types>::type>
型のオブジェクト(ひとまずそれ_Mybargsと呼ぶ)で、このtupleの各要素のタイプはdecay<Args_i>::type
で、bindを呼び出すと、2番目のパラメータargs...が転送され、すなわちforward<Args_i>(arg_i)
、Args_iはargsのi番目を表す.一般的に、このオブジェクトはbind時に渡されたすべてのパラメータargs....retのこの2つのメンバーオブジェクトはbindから伝達された関数とパラメータをそれぞれ保存して将来の呼び出しタスクを実現する.
2.コンストラクション関数:
3.メンバータイプ:result_type
auto ret = bind<Ret>(fn , args)
である場合、result_typeはRet.4.メンバー関数operator()(これに重点を置いてbindの戻り値retは、関数呼び出しとして使用される)
retはbindの戻り値としてret:ret(a 1,a 2,a 3,...ai)を呼び出すと仮定する.このときret内部に保存されている
decay<Fn>::type
タイプのオブジェクト:MyFunは、a 1,a 2,...,aiの値をバインドするために呼び出されます.reference_wrapper<T>
タイプが指定されている場合、例えばbindを呼び出すときにstd::refまたはstd::crefを使用してargsをパッケージする場合、ret内部の_MyFunの場合、対応パラメータはT&タイプで入力されます_MyFun. std::is_bind_expression<decltype(arg)>::value == true
となるargが存在する場合、このネストされたbind式は直ちに呼び出され、その戻り値はretに渡される_MyFunはパラメータ(つまりネストbindの戻り値をret呼び出し時のパラメータとする)として、ネストbindにプレースホルダplaceholderが使用する場合、これらのplaceholderはretの呼び出しパラメータret(a 1,a 2,...ai)から対応する位置を選択する.std::is_placeholder<T>::value != 0
).ではa 1,a 2,...,aiは転送形式forward<ai>(ai)
で_に渡されるMyFun,a 1対応_1,a 2対応_2,このように推す.g(a 1,a 2,...,ai)では、bindを呼び出すとplaceholderが_1,g(a 1,a 2,a 3)では、a 2,a 3はマッチングされず、マッチングされていないパラメータは評価されるが、破棄される.
gがvolatile(volatile or const volatile)と指定する場合、結果は定義されない.
「あんなにたくさん言ったのに、bを装いたいんだ」とbindの実際のコードでの応用を見てみましょう.
2つの極端な状況からbindプロセスとstd::placeholdersの使用を正しく理解する
bindをより理解しやすく使用するために、fnのパラメータは値にバインドされるかplaceholdersにバインドされるかを強調します.
極端1:値に完全にバインド
void f(int n1, int n2, int n3)
{
cout << n1 << " " << n2 << " " << n3 << endl;
}
// f , , empty_args
auto empty_args = bind(f, 1, 2, 3);
empty_args(); // 1 2 3
極端2:std::placeholdersに完全にバインド
void f(int n1, int n2, int n3)
{
cout << n1 << " " << n2 << " " << n3 << endl;
}
int ret4()
{
cout << "ret4() called" << endl;
return 4;
}
// f ,
auto need_3args = bind(f, _1, _2, _3);
need_3args(1, 2, 3); // 1 2 3
need_3args(1, 2, 3, 4, 5); // 1 2 3; 4 5
need_3args(1, 2, 3, ret4()); // ret4() called<cr> 1 2 3; ret4(), 4
この2つの極端な場合を除いて,bindはほとんどの場合,値とプレースホルダを混合してバインドされる.
bindプロセス分析及び呼び出しパラメータ制御
bindのバインドプロセスを解析するときに、bind呼び出しにエラーがあるかどうか、bind戻り値retを呼び出すときにどのように正しくパラメータを渡すかをどのように決定しますか?たとえば、次のbind呼び出しを行います.
void f(int n1, int n2, int n3, int n4, int n5)
{
cout << n1 << " " << n2 << " " << n3 << " " << n4 << " " << n5 << endl;
}
// bind
auto mix1 = bind(f, 1, 2, 3, _1, _2);
auto mix2 = bind(f, 1, 2, 3, 4, _1, _2);
auto mix3 = bind(f, 1, 2, 3, _2, _1);
auto mix4 = bind(f, _3, 2, 3, 4, _1);
auto mix5 = bind(f, _1, _1, _1, _1, _1);
auto mix6 = bind(f, _100, _50, _10, _5, _1);
// mix , ?
// mix1(...);
// mix3(...);
// mix4(...);
// mix5(...);
分析プロセス:
1.bind(f,args...)の合法性分析
fに必要なパラメータの個数をNとする、bind(f…)において、与えられた値の個数をVとし、与えるプレースホルダの個数をSとする.
合法的なbind呼び出しの場合、必ずN==V+Sがある.V+SがNを超えているか、N未満である場合、コンパイルはエラーを報告する.
したがって、上記のmix定義では、mix 1、mix 3、mix 4、mix 5のみが合法である.mix 2のパラメータ個数はすでに6個あるが,fは5個しか必要としない.mix 6のパラメータの個数ペアですが、プレースホルダが大きすぎて、VC++(2013)コンパイラ実装で最大20です.
2.bind戻り値retの呼び出し参照書き方
bind(f,args...)で最大のプレースホルダを_とするM.(例えばmix 1,mix 2,mix 3中M=2;mix 4中M=3;mix 5中M=1;mix 6中M=100;
次のようになります.
したがって、上記で定義した正当なmixの呼び出し例および出力は、以下のようにすることができる.
auto mix1 = bind(f, 1, 2, 3, _1, _2);
auto mix3 = bind(f, 1, 2, 3, _2, _1);
auto mix4 = bind(f, _3, 2, 3, 4, _1);
auto mix5 = bind(f, _1, _1, _1, _1, _1);
mix1(4, 5); // 1 2 3 4 5; M = 2;
//mix1(4); // no,
mix3(5, 4); // 1 2 3 4 5; M = 2;
mix4(5, 0, 1); // 1 2 3 4 5; M = 3; 5 _1, 1 _3, 0 , bind _2.
mix5(5); // 5 5 5 5 5; M = 1;
mix 6の中の_に気づくかもしれません100, _50、このような大きなプレースホルダはコンパイルできません.
placeholdersの最大値はVC++で20であり、その最大値は具体的なコンパイラに依存して実現され、この最大値にこだわる必要はない.大きなプレースホルダは一般的には使用されません.プレースホルダを使用すると100、1つしか使っていませんが、これは呼び出し者が少なくとも100のパラメータを提供する必要があることを意味します.あなたは誰を殺したいですか?
次に、bindの呼び出し例をfnの分類に従って示す.
std::bindバインド一般関数、lambda式
#include <functional>
// , 、
double multiply(double d1, double d2)
{
return d1 * d2;
}
double divide(double d1, int n)
{
assert(n != 0);
return d1 / n;
}
//----------------------------- begin of new test -----------------------------
RUN_GTEST(FunctorTest, Bind, @); // google gtest , !
using std::bind; // for std::bind
using namespace std::placeholders; // for _1, _2, _3 ...
// “ ”, same multiply , ,
auto same = bind(multiply, _1, _2);
EXPECT_EQ(200.0, same(2, 100));
// , 2.0, ,
// 2 。
auto doublize = bind(multiply, _1, 2.0);
EXPECT_EQ(200.0, doublize(100));
// ,ret_20 , 20
auto ret_20 = bind(multiply, 2, 10);
EXPECT_EQ(20.0, ret_20());
// , arg1/arg2
double d1 = divide(10, 2);
EXPECT_EQ(5, d1);
// bind, ,revertDivide(arg1, arg2) arg2/arg1
auto revertDivide = bind(divide, _2, _1);
double d2 = revertDivide(10, 2);
EXPECT_EQ(1 / 5.0, d2);
// bind , int
auto rounding = bind<int>(divide, _1, _2);
auto i1 = rounding(10, 3);
bool isSameType = is_same<int, decltype(i1)>::value; // i1 int
EXPECT_TRUE(isSameType);
EXPECT_EQ(3, i1);
// bind lambda
auto lambda_func = [](int x) -> int { return x; };
auto ret_100 = bind(lambda_func, 100);
EXPECT_EQ(100, ret_100);
// bind
std::function<void(int)> func_with_1args;
func_with_1args = bind(multiply, 10, _1);
END_TEST;
std::bindバインドクラスメンバー関数、メンバー変数
メンバー関数が一般的な関数と異なる特殊な点は、最初のパラメータがそのタイプのオブジェクト(またはオブジェクトのポインタまたは参照)である必要があることです.
class Foo
{
public:
void f(int n1, int n2, int n3)
{
cout << n1 << " " << n2 << " " << n3 << endl;
}
int a_ { 100 };
};
//----------------------------- begin of new test -----------------------------
RUN_GTEST(FunctorTest, Bind, @);
Foo foo;
Foo& foo_ref= foo;
// :Foo::f(int n1, int n2, int n3);
// Foo::f, ,1.Foo ( ); 2~4 n1, n2, n3
// mfarg4 4 : _1, _2, _3, _4.
auto mfarg4 = bind(&Foo::f, _1, _2, _3, _4);
//
mfarg4(foo, 10, 20, 30); // 10 20 30;
//
mfarg4(&foo, 10, 20, 30); // 10 20 30;
//
mfarg4(foo_ref, 10, 20, 30); // 10 20 30;
// mfarg3 : ,f 30.
auto mfarg3 = bind(&Foo::f, _1, _2, _3, 30);
mfarg3(foo, 10, 20); // 10 20 30;
// mfarg2 : ,f 20, 30.
auto mfarg2 = bind(&Foo::f, _1, _2, 20, 30);
mfarg2(foo, 10); // 10 20 30;
// mfarg1 foo , , bind 10,20,30.
auto mfarg1 = bind(&Foo::f, _1, 10, 20, 30);
mfarg1(foo); // 10 20 30;
// , mfarg0 .
auto mfarg0 = bind(&Foo::f, foo, 10, 20, 30);
mfarg0(); // 10 20 30
//
auto mfarg01 = bind(&Foo::f, &foo, 10, 20, 30);
mfarg01(); // 10 20 30
auto mfarg02 = bind(&Foo::f, foo_ref, 10, 20, 30);
mfarg02(); // 10 20 30
// std::function
std::function<void(int, int, int)> normal_func;
normal_func = bind(&Foo::f, foo, _1, _2, _3);
normal_func(10, 20, 30); // 10 20 30
//------------------------------ a_ ---------------------------
// bind , ( ), !!
auto bind_mv = bind(&Foo::a_, _1);
cout << bind_mv(foo); // 100
cout << bind_mv(foo_ref); // 100
//cout << bind_mv(&foo); // error,
END_TEST;
std::bindバインドテンプレート関数
// , , 。 c++11 trailing return types .
template <typename T1, typename T2>
auto add(const T1 & t1, const T2& t2) -> decltype(t1 + t2)
{
return t1 + t2;
}
// work with template function.
auto addby2 = bind(add<double, double>, _1, 2.0);
cout << addby2(10.2); // 12.2
ネストstd::bind共有std::placeholder.
void print(int n1, int n2, int n3)
{
cout << n1 << " " << n2 << " " << n3 << endl;
}
//
auto addby1 = [] (int x) -> int
{
cout << "addby1() called" << endl;
return (x+1);
};
// bind bind() placeholders
auto nested_f = bind(print, _1, bind(addby1, _1), _2);
nested_f(1, 3); // addby1() called<cr> 1 2 3
reference_wrapper<T>
タイプ、バインド参照を実現int x(10);
// x, x
auto bind_ref = bind(print, 1, std::cref(x), x);
bind_ref(); // 1 10 10;
x = 100;
bind_ref(); // 1 100 10; x ,
bindと標準ライブラリの連携
RUN_GTEST(FunctorTest, BindPredefinedFunctors, @);
// all predefined functors:
// negate, plus, minus, multiplies, divides, modulus, equal_to,
// not_equal_to, less, greater, less_equal, greater_equal,
// logical_not, logical_and, logical_or, bit_and, bit_or, bit_xor
auto tenTimes = bind(multiplies<int>(), _1, 10);
EXPECT_EQ(100, tenTimes(10));
EXPECT_EQ(200, tenTimes(20));
EXPECT_EQ(300, tenTimes(30));
vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8 };
// nested bind. output v[i] if 10*v[i] > 50.
copy_if(v.begin(), v.end(),
ostream_iterator<int>(cout, ", "),
bind(greater<int>(),
bind(multiplies<int>(), _1, 10),
50)); // 6,7,8,
cr;
END_TEST;
bindとスマートポインタ
RUN_GTEST(FunctorTest, BindSmartPointer, @);
struct Temp
{
Temp(int i=0) : i_(i) {}
void print() { pln(i_); }
int i_;
};
vector<shared_ptr<Temp>> vs =
{
shared_ptr<Temp>(new Temp(1)),
shared_ptr<Temp>(new Temp(2)),
shared_ptr<Temp>(new Temp(3)),
};
for_each(vs.begin(), vs.end(), bind(&Temp::print, _1)); // 1<cr>2<cr>3<cr>
bind(&Temp::print, vs[0])(); // 1
bind(&Temp::print, vs[1])(); // 2
bind(&Temp::print, vs[2])(); // 3
END_TEST;
last but not least
bindのパラメータはcopyまたはmoveによってターゲット関数に渡され、指定された参照転送が表示されない限り、
std::ref
またはstd::cref
でパラメータをラップします.そうしないと、参照によって転送されません.これは、bindの大きなオブジェクトがパラメータとして存在する可能性のあるコピーオーバーヘッドが、できるだけ引用**であることを認識することを意味します.また、moveはオブジェクトの状態を変更するため、bindパラメータリストでplaceholdersを再利用する場合は、パラメータがmovedによって削除された場合を考慮して、パラメータが左値または移動できない右値の場合にのみplaceholdersを再利用することを推奨します.
ソースコード
githubブログへようこそ、当駅と同期して更新します