std::bind
在标头 <functional> 定义
|
||
(1) | ||
template< class F, class... Args > /* 未指定 */ bind( F&& f, Args&&... args ); |
(C++11 起) (C++20 前) |
|
template< class F, class... Args > constexpr /* 未指定 */ bind( F&& f, Args&&... args ); |
(C++20 起) | |
(2) | ||
template< class R, class F, class... Args > /* 未指定 */ bind( F&& f, Args&&... args ); |
(C++11 起) (C++20 前) |
|
template< class R, class F, class... Args > constexpr /* 未指定 */ bind( F&& f, Args&&... args ); |
(C++20 起) | |
函数模板 std::bind
生成 f 的转发调用包装器。调用此包装器等价于以一些绑定到 args 的参数调用 f。
如果 std::is_constructible<std::decay<F>::type, F>::value 是 false,或 std::is_constructible<std::decay<Arg_i>::type, Arg_i>::value 对于 Args
中的某个类型 Arg_i
是 false,那么程序非良构。
如果 std::decay<Ti>::type 或 Args
中的某个类型不可移动构造 (MoveConstructible) 或可析构 (Destructible) ,那么行为未定义。
参数
f | - | 可调用 (Callable) 对象(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针) |
args | - | 要绑定的参数列表,未绑定参数会被命名空间 std::placeholders 的占位符 _1, _2, _3... 替换
|
返回值
某个未指定类型 T
的函数对象 g,其中 std::is_bind_expression<T>::value 是 true。它有下列属性:
std::bind 返回类型
成员对象
std::bind
的返回类型保有从 std::forward<F>(f) 构造的 std::decay<F>::type 类型成员对象,和对于 args... 中每个参数的类型是 std::decay<Arg_i>::type 的各一个对象,类似地从 std::forward<Arg_i>(arg_i) 构造。
构造函数
如果 std::bind
的返回类型的所有成员类型(说明如上)都可复制构造 (CopyConstructible) 那么它可复制构造 (CopyConstructible) ,否则它可移动构造 (MoveConstructible) 。类型定义下列成员:
成员类型
|
(C++20 前) |
成员函数 operator()
从函数调用表达式 g(u1, u2, ... uM) 调用 g 时,发生被存储对象的调用,如同以
INVOKE
(fd, std::forward<V1>(v1), std::forward<V2>(v2), ..., std::forward<VN>(vN)) 或INVOKE<R>
(fd, std::forward<V1>(v1), std::forward<V2>(v2), ..., std::forward<VN>(vN)),其中 fd 是 std::decay<F>::type 类型的值,受绑定参数 v1,
v2, ...,
vN 参数的值和类型按以下方式确定。
如果在到 g() 调用的中提供一些参数与 g 存储的任何占位符都不匹配,那么求值并忽略未使用的参数。
operator() 调用当且仅当底层 INVOKE
操作不抛出或是常量子表达式 (C++20 起)时也具有这些属性。operator() 只有在 INVOKE
操作在作为不求值操作数良构时才会参与重载决议。
如果 g 具有 volatile 限定,那么程序非良构。
如果 INVOKE
(fd, w1, w2, ..., wN) 对于任何值 w1,
w2, ...,
wN 都不合法,那么行为未定义。
受绑定参数
对于每个存储的参数 arg_i,INVOKE
或 INVOKE<R>
操作中的受绑定参数 v_i 通过以下方式确定:
第1种情况:引用包装
如果 arg_i 拥有类型 std::reference_wrapper<T>(例如,在起始的到 std::bind
调用中使用了 std::ref 或 std::cref),那么 v_i 是 arg_i.get() 且它的类型 V_i
是 T&
:存储的参数按引用传递进入被调用的函数对象。
第2种情况:绑定表达式
如果 arg_i 拥有类型 T
并且 std::is_bind_expression<T>::value 是 true(例如,直接传递到起始的对 std::bind
调用的另一 std::bind
表达式),那么 std::bind
进行函数组合:不是传递该绑定子表达式将返回的函数对象,而是立即求值该子表达式,并将它的值传递给外层可调用对象。如果 bind 子表达式拥有任何占位符参数,那么将它们与外层 bind 共享(从 u1,
u2, ...
中选出)。特别是,v_i 是 arg_i(std::forward<Uj>(uj)...) 而它的类型 V_i
是 std::result_of<T cv &(Uj&&...)>::type&& (C++17 前)std::invoke_result_t<T cv &, Uj&&...>&& (C++17 起)(cv 限定与 g 的相同)。
第3种情况:占位符
如果 arg_i 拥有类型 T
并且 std::is_placeholder<T>::value 不是 0(表示以如 std::placeholders::_1, _2, _3, ...
的占位符为到 std::bind
初始调用的参数),那么将占位符所指示的参数(_1 的 u1、_2 的 u2 等)传递给可调用对象:v_i 是 std::forward<Uj>(uj) 而它的类型 V_i
是 Uj&&。
第4种情况:通常参数
否则,arg_i 会作为左值参数传递给可调用对象:v_i 单纯地是 arg_i 而它的类型 V_i
是 T
cv &
,其中 cv 是与 g 相同的 cv 限定。
异常
只有在从 std::forward<F>(f) 构造 std::decay<F>::type 抛出,或从 std::forward<Arg_i>(arg_i) 构造对应的任何 std::decay<Arg_i>::type 抛出的情况下才会抛出异常,其中 Arg_i
是 Args... args
中的第 i 个类型,而 arg_i 是第 i 个参数。
注解
如可调用 (Callable) 中描述,调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与 std::unique_ptr),指向将访问它的成员的对象。
到 std::bind
的参数被复制或移动,而且决不按引用传递,除非以 std::ref 或 std::cref 包装。
允许同一 bind 表达式中的多重占位符(例如多个 _1),但结果只有在对应参数(u1)是左值或不可移动右值时才有良好定义。
示例
#include <functional> #include <iostream> #include <memory> #include <random> void f(int n1, int n2, int n3, const int& n4, int n5) { std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n'; } int g(int n1) { return n1; } struct Foo { void print_sum(int n1, int n2) { std::cout << n1 + n2 << '\n'; } int data = 10; }; int main() { using namespace std::placeholders; // 对于 _1, _2, _3... std::cout << "1) 参数重排序和按引用传递:"; int n = 7; // ( _1 与 _2 来自 std::placeholders ,并表示将来会传递给 f1 的参数) auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n); n = 10; f1(1, 2, 1001); // 1 为 _1 所绑定, 2 为 _2 所绑定,不使用 1001 // 进行到 f(2, 42, 1, n, 7) 的调用 std::cout << "2) 使用 lambda 达成相同效果:"; n = 7; auto lambda = [&ncref = n, n](auto a, auto b, auto /* 未使用 */) { f(b, 42, a, ncref, n); }; n = 10; lambda(1, 2, 1001); // 等同于调用 f1(1, 2, 1001) std::cout << "3) 嵌套 bind 子表达式共享占位符:"; auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5); f2(10, 11, 12); // 进行到 f(12, g(12), 12, 4, 5); 的调用 std::cout << "4) 以分布绑定随机数生成器:"; std::default_random_engine e; std::uniform_int_distribution<> d(0, 10); std::function<int()> rnd = std::bind(d, e); // e 的一个副本存储于 rnd for (int n = 0; n < 10; ++n) std::cout << rnd() << ' '; std::cout << '\n'; std::cout << "5) 绑定指向成员函数指针:"; Foo foo; auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1); f3(5); std::cout << "6) 绑定是指向成员函数指针的 mem_fn:"; auto ptr_to_print_sum = std::mem_fn(&Foo::print_sum); auto f4 = std::bind(ptr_to_print_sum, &foo, 95, _1); f4(5); std::cout << "7) 绑定指向数据成员指针:"; auto f5 = std::bind(&Foo::data, _1); std::cout << f5(foo) << '\n'; std::cout << "8) 绑定是指向数据成员指针的 mem_fn:"; auto ptr_to_data = std::mem_fn(&Foo::data); auto f6 = std::bind(ptr_to_data, _1); std::cout << f6(foo) << '\n'; std::cout << "9) 使用智能指针调用被引用对象的成员:"; std::cout << f6(std::make_shared<Foo>(foo)) << '\n' << f6(std::make_unique<Foo>(foo)) << '\n'; }
输出:
1) 参数重排序和按引用传递:2 42 1 10 7 2) 使用 lambda 达成相同效果:2 42 1 10 7 3) 嵌套 bind 子表达式共享占位符:12 12 12 4 5 4) 以分布绑定随机数生成器:0 1 8 5 5 2 0 7 7 10 5) 绑定指向成员函数指针:100 6) 绑定是指向成员函数指针的 mem_fn:100 7) 绑定指向数据成员指针:10 8) 绑定是指向数据成员指针的 mem_fn:10 9) 使用智能指针调用被引用对象的成员:10 10
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
LWG 2021 | C++11 | 1. 绑定参数没有转发到 fd 2. 在第2种情况下, V_i 是std::result_of<T cv (Uj...)>::type |
1. 转发绑定参数 2. 更改成 std::result_of<T cv &(Uj&&...)>::type&& |
参阅
(C++20) |
按顺序绑定一定数量的参数到函数对象 (函数模板) |
(C++11) |
用作 std::bind 表达式中的未绑定实参的占位符 (常量) |
(C++11) |
从成员指针创建出函数对象 (函数模板) |