std::bind
在标头 <functional> 定义
|
||
template< class F, class... Args > /* 未指定 */ bind( F&& f, Args&&... args ); |
(1) | (C++11 起) (C++20 起为 constexpr ) |
template< class R, class F, class... Args > /* 未指定 */ bind( F&& f, Args&&... args ); |
(2) | (C++11 起) (C++20 起为 constexpr ) |
函数模板 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<F>::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 存储的任何占位符都不匹配,那么求值并丢弃未使用的形参。
当且仅当底层 INVOKE
操作不抛出或是常量子表达式 (C++20 起)时,operator() 的调用也是这样。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
进行函数组合:不是传递该绑定子表达式将返回的函数对象,而是立即求值该子表达式,并将它的值传递给外层可调用对象。如果绑定子表达式拥有任何占位符实参,那么就与外层绑定(从 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::ref 或 std::cref 包装。
允许同一绑定表达式中使用重复的占位符(例如多个 _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) 嵌套的绑定子表达式共享占位符:"; 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) 嵌套的绑定子表达式共享占位符: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++23) |
按顺序绑定一定数量的实参到函数对象 (函数模板) |
(C++11) |
用作 std::bind 表达式中的未绑定实参的占位符 (常量) |
(C++11) |
从成员指针创建出函数对象 (函数模板) |