C++ 23 实用工具(二)绑定工具
Adaptors for Functions
std::bind
、std::bind_front
、std::bind_back
和std::function
这四个函数非常适合一起使用。
其中,std::bind
、std::bind_front
和std::bind_back
可以让您即时创建新的函数对象,而std::function
则可以将这些临时的函数对象绑定到变量上。
然而,在C++中,这四个函数大多数情况下都是多余的。首先,您可以使用lambda
表达式代替std::bind
、std::bind_front
和std::bind_back
;其次,您通常可以使用自动类型推导的auto
关键字代替std::function
。
因此,尽管在某些特定场景下,这些函数仍然是有用的,但在大多数情况下,它们并不是必须的。
std::bind
#include <functional>
#include <iostream>
using namespace std::placeholders;
double divMe(double a, double b) { return a / b; };
int main()
{
std::function<double(double, double)> myDiv1 = std::bind(divMe, _1, _2);
std::function<double(double)> myDiv2 = std::bind(divMe, 2000, _1);
std::function<double(double)> myDiv3 = std::bind_front(divMe, 2000);
std::function<double(double)> myDiv4 = std::bind_back(divMe, 10);
std::cout << "myDiv1(1000, 5) = " << myDiv1(1000, 5) << std::endl; // 200
std::cout << "myDiv2(10) = " << myDiv2(10) << std::endl; // 200
std::cout << "myDiv3(5) = " << myDiv3(5) << std::endl; // 400
std::cout << "myDiv4(2000) = " << myDiv4(2000) << std::endl; // 200
return 0;
}
借助std::bind
,您可以以多种方式创建函数对象:
- 将参数绑定到任意位置
- 改变参数的顺序
- 引入占位符
- 部分求值函数
通过std::bind
创建的新函数对象可以被调用、用于STL算法或者存储在std::function
中。
std::bind_front (C++20)
std::bind_front
函数可以从可调用对象创建可调用包装器。调用std::bind_front(func, arg...)
会将所有参数arg
绑定到func
的前面,并返回一个可调用包装器。
std::bind_back (C++23)
std::bind_back
函数可以从可调用对象创建可调用包装器。调用std::bind_back(func, arg...)
会将所有参数arg
绑定到func
的后面,并返回一个可调用包装器。
std::function
std::function
函数可以将任意可调用对象存储到变量中,它是一个多态的函数包装器。可调用对象可以是lambda函数、函数对象或者函数。如果需要显式指定可调用对象的类型,则必须使用std::function
,它无法被auto
关键字替换。
std::tie 和 std::ignore
std::tie
函数可以创建引用变量的元组。当您需要同时返回多个值时,可以使用std::tie
函数将这些值打包成一个元组返回,并通过引用将元组的内容解包到变量中。
例如,假设您有一个返回两个值的函数foo()
,您可以使用std::tie
函数将这两个值打包成一个元组返回,并通过引用将元组的内容解包到两个变量中:
int x, y;
std::tie(x, y) = foo();
如果您不需要元组中的某个元素,则可以使用std::ignore
函数将其忽略。例如,假设您只需要元组中的第一个值,可以将第二个值用std::ignore
函数忽略:
int x, y;
std::tie(x, std::ignore) = foo();
这样,foo()
返回的元组中的第二个值将被忽略。
#include <tuple>
#include <iostream>
using namespace std;
int main()
{
int first = 1;
int second = 2;
int third = 3;
int fourth = 4;
cout << first << " " << second << " " << third << " " << fourth << endl; // 1 2 3 4
auto tup = std::tie(first, second, third, fourth) = std::make_tuple(101, 102, 103, 104); // 绑定元组并赋值
cout << get<0>(tup) << " " << get<1>(tup) << " " << get<2>(tup) << " " << get<3>(tup) << endl; // 101 102 103 104
cout << first << " " << second << " " << third << " " << fourth << endl; // 101 102 103 104
first = 201;
get<1>(tup) = 202;
cout << get<0>(tup) << " " << get<1>(tup) << " " << get<2>(tup) << " " << get<3>(tup) << endl; // 201 202 103 104
cout << first << " " << second << " " << third << " " << fourth << endl; // 201 202 103 104
int a, b;
tie(std::ignore, a, std::ignore, b) = tup;
cout << a << " " << b << endl; // 202 104
return 0;
}
Reference Wrappers
Reference Wrappers是一个定义在头文件中的可复制构造和可复制赋值的包装器,用于类型&的对象。它具有像引用一样的行为,但可以被复制。与传统引用不同,std::reference_wrapper对象支持两个附加用例:
- 您可以在标准模板库的容器中使用它们。例如:
std::vector<std::reference_wrapper<int>> myIntRefVector
- 您可以复制具有std::reference_wrapper对象的类的实例。通常情况下,这对于引用是不可能的。
get
成员函数允许访问引用:myInt.get()
。您可以使用引用包装器来封装和调用可调用对象。
Reference Wrappers在处理需要使用引用的情况时非常有用,同时也允许在STL容器中存储引用类型的对象,这是传统引用无法做到的。因此,它是C++中一个非常方便的工具。
#include <functional>
#include <iostream>
void foo()
{
std::cout << "被调用了" << '\n';
}
typedef void callableUnit();
std::reference_wrapper<callableUnit> refWrap(foo);
int main()
{
refWrap(); // 输出 "被调用了"
return 0;
}
这段代码定义了一个名为foo
的函数,它没有参数和返回值。接下来,我们定义了一个名为callableUnit
的函数类型别名,它代表没有参数和返回值的函数类型。然后,我们使用std::reference_wrapper
将foo
包装到refWrap
中。
在main
函数中,我们直接调用refWrap()
,这实际上会调用foo()
函数并输出"被调用了"。由于refWrap
是一个std::reference_wrapper
对象,它可以像函数一样被调用。