std::function
-
std::function 是函数模板类(是一个类),定义在头文件 functional -
类模板 std::function 是一个通用的多态函数包装器。std::function 的实例可以存储、复制和调用任何 可复制构造、可调用的目标函数、lambda 表达式、绑定表达式或其他函数对象,以及指向成员函数的指针和指向数据成员的指针。std::function 对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。 -
存储的可调用对象称为 std::function 的目标。 如果 std::function 不包含目标,则称为空。 调用空 std::function 的目标会导致抛出 std::bad_function_call 异常。 -
成员函数:
operator= 或者assign 分配一个新目标operator bool 检测是否包含目标operator () 调用目标 -
注意:
-
当 std::function 的结果类型是从没有尾随返回类型的 lambda 表达式初始化的引用时,应小心。 由于自动推导的工作方式,这样的 lambda 表达式将始终返回一个纯右值。 因此,结果引用通常会绑定到一个临时对象,该临时对象的生命周期在 std::function::operator() 返回时结束。 std::function<const int&()> F([]{ return 42; });
int x = F();
-
案例: #include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
std::function<void(int)> f_display = print_num;
f_display(-9);
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n'
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18); == 18
auto factorial = [](int n) {
std::function<int(int)> fac = [&](int n){ return (n < 2) ? 1 : n*fac(n-1); };
return fac(n);
};
for (int i{5}; i != 8; ++i) { std::cout << i << "! = " << factorial(i) << "; "; }
}
std::ref/cref
解释:
std::ref 用于包装按引用传递的值。std::cref 用于包装按const 引用传递的值。
作用:
- 函数模板
ref 和cref 是辅助函数,它们生成std::reference_wrapper 类型的对象,使用模板参数推导来确定结果的模板参数。 bind() 是一个函数模板,它的原理是根据已有的模板,生成一个函数,但是由于bind() 不知道生成的函数执行的时候,传递进来的参数是否还有效。所以它选择参数值传递而不是引用传递。如果想引用传递,std::ref 和std::cref 就派上用场了。
案例:
void f(int n1, int n2, int n3, const int& n4, int n5) {
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
void main() {
int n = 7;
auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
n = 10;
f1(1, 2, 1001);
}
output:
2 42 1 10 7
std::mem_fn
- 函数模板
std::mem_fn 为指向成员的指针生成包装对象,它可以存储、复制和调用指向成员的指针。 调用 std::mem_fn 时可以使用对对象的引用和指针(包括智能指针)。 - Param:指向将被包装的成员的指针
- 案例:
#include <functional>
#include <iostream>
#include <memory>
struct Foo {
void display_greeting() {
std::cout << "Hello, world.\n";
}
void display_number(int i) {
std::cout << "number: " << i << '\n';
}
int add_xy(int x, int y) {
return data + x + y;
}
template <typename... Args> int add_many(Args... args) {
return data + (args + ...);
}
auto add_them(auto... args) {
return data + (args + ...);
}
int data = 7;
};
int main() {
auto f = Foo{};
auto greet = std::mem_fn(&Foo::display_greeting);
greet(f);
auto print_num = std::mem_fn(&Foo::display_number);
print_num(f, 42);
auto access_data = std::mem_fn(&Foo::data);
std::cout << "data: " << access_data(f) << '\n';
auto add_xy = std::mem_fn(&Foo::add_xy);
std::cout << "add_xy: " << add_xy(f, 1, 2) << '\n';
auto u = std::make_unique<Foo>();
std::cout << "access_data(u): " << access_data(u) << '\n';
std::cout << "add_xy(u, 1, 2): " << add_xy(u, 1, 2) << '\n';
auto add_many = std::mem_fn(&Foo::add_many<short, int, long>);
std::cout << "add_many(u, ...): " << add_many(u, 1, 2, 3) << '\n';
auto add_them = std::mem_fn(&Foo::add_them<short, int, float, double>);
std::cout << "add_them(u, ...): " << add_them(u, 5, 7, 10.0f, 13.0) << '\n';
}
std::bind
-
std::bind 是函数模板(是一个函数),定义在头文件 functional -
函数模板绑定为 f 生成一个转发调用包装器。调用此包装器等效于调用 f 并将其一些参数绑定到 args。
- f:将绑定到某些参数的可调用对象(函数对象、函数指针、函数引用、成员函数指针或数据成员指针)
- args:要绑定的参数列表,未绑定的参数由命名空间 std::placeholders 的占位符 _1、_2、_3… 替换
-
案例: #include <random>
#include <iostream>
#include <memory>
#include <functional>
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;
std::cout << "1) argument reordering and pass-by-reference: ";
int n = 7;
auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
n = 10;
f1(1, 2, 1001);
std::cout << "2) achieving the same effect using a lambda: ";
n = 7;
auto lambda = [ncref=std::cref(n), n=n](auto a, auto b, auto ) {
f(b, 42, a, ncref, n);
};
n = 10;
lambda(1, 2, 1001);
std::cout << "3) nested bind subexpressions share the placeholders: ";
auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
f2(10, 11, 12);
std::cout << "4) bind a RNG with a distribution: ";
std::default_random_engine e;
std::uniform_int_distribution<> d(0, 10);
auto rnd = std::bind(d, e);
for(int n=0; n<10; ++n)
std::cout << rnd() << ' ';
std::cout << '\n';
std::cout << "5) bind to a pointer to member function: ";
Foo foo;
auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
f3(5);
std::cout << "6) bind to a mem_fn that is a pointer to member function: ";
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) bind to a pointer to data member: ";
auto f5 = std::bind(&Foo::data, _1);
std::cout << f5(foo) << '\n';
std::cout << "8) bind to a mem_fn that is a pointer to data member: ";
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) use smart pointers to call members of the referenced objects: ";
std::cout << f6(std::make_shared<Foo>(foo)) << ' '
<< f6(std::make_unique<Foo>(foo)) << '\n';
}
-
输出: 1) argument reordering and pass-by-reference: 2 42 1 10 7
2) achieving the same effect using a lambda: 2 42 1 10 7
3) nested bind subexpressions share the placeholders: 12 12 12 4 5
4) bind a RNG with a distribution: 0 1 8 5 5 2 0 7 7 10
5) bind to a pointer to member function: 100
6) bind to a mem_fn that is a pointer to member function: 100
7) bind to a pointer to data member: 10
8) bind to a mem_fn that is a pointer to data member: 10
9) use smart pointers to call members of the referenced objects: 10 10
Lambda 表达式
-
在C++11 及更高版本中,lambda 表达式(通常称为 lambda )是一种简便方法,用于定义匿名函数对象 (在作为参数传递给函数的位置 ) 一个 闭包) 。 通常,lambda 用于封装传递给算法或异步函数的一些代码行。 -
案例: 作为第三个参数传递给 std::sort() 函数 #include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
[](float a, float b) {
return (std::abs(a) < std::abs(b));
}
);
}
-
lambda 的组成部分:
- [capture list] (params list) mutable exception-> return type { function body }
- capture list:捕获外部变量列表。
- Lambda表达式的引入标志,在‘[]’里面可以填入‘=’或‘&’表示该lambda表达式“捕获”(lambda表达式在一定的scope可以访问的数据)的数据时以什么方式捕获的,
- ‘&’表示一引用的方式;
- ‘=’表明以值传递的方式捕获,除非专门指出。
- params list:形参列表
- mutable指示符:用来说用是否可以修改捕获的变量
- exception:异常设定,异常标识
- return type:返回类型
- function body:“函数体”,也就是lambda表达式需要进行的实际操作
案例: int main()
{
[]() noexcept { throw 5; }();
}
-
可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种:
- [capture list] (params list) -> return type {function body}
- [capture list] (params list) {function body}
- [capture list] {function body}
-
捕获外部变量
- Lambda表达式可以使用其可见范围内的外部变量,但必须明确声明(明确声明哪些外部变量可以被该Lambda表达式使用)。那么,在哪里指定这些外部变量呢?
- Lambda表达式通过在最前面的方括号[]来明确指明其内部可以访问的外部变量,这一过程也称过Lambda表达式“捕获”了外部变量。
#include <iostream>
using namespace std;
int main()
{
int a = 123;
auto f = [a] { cout << a << endl; };
f();
auto x = [](int a){cout << a << endl;}(123);
}
-
上面这个例子先声明了一个整型变量a,然后再创建Lambda表达式,该表达式“捕获”了a变量,这样在Lambda表达式函数体中就可以获得该变量的值。 -
类似参数传递方式(值传递、引入传递、指针传递),在Lambda表达式中,外部变量的捕获方式也有值捕获、引用捕获、隐式捕获。 -
值捕获:
- 值捕获和参数传递中的值传递类似,被捕获的变量的值在Lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响影响Lambda表达式中的值。
int main()
{
int a = 123;
auto f = [a] { cout << a << endl; };
a = 321;
f();
}
这里需要注意的是,如果以传值方式捕获外部变量,则在Lambda表达式函数体中不能修改该外部变量的值。 -
引用捕获
- 使用引用捕获一个外部变量,只需要在捕获列表变量前面加上一个引用说明符&
int main()
{
int a = 123;
auto f = [&a] { cout << a << endl; };
a = 321;
f();
}
引用捕获的变量使用的实际上就是该引用所绑定的对象。 -
隐式捕获
- 上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。
int main()
{
int a = 123;
auto f = [=] { cout << a << endl; };
f();
}
int main()
{
int a = 123;
auto f = [&] { cout << a << endl; };
a = 321;
f();
}
-
Lambda表达式的参数
- 在Lambda表达式中传递参数还有一些限制,主要有以下几点:
- 参数列表中不能有默认参数
- 不支持可变参数
- 所有参数必须有参数名
这个Eg: int m = [](int x) { return [](int y) { return y * 2; }(x)+6; }(5);
std::cout << "m:" << m << std::endl;
|