【C++11】C++11 中的std::function和std::bind
std::function
是通用多态函数封装器。 std::function
的实例能存储、复制及调用任何可调用 (Callable) 目标——函数、 lambda
表达式、 bind
表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
std::function
是一个泛化的函数,它可以代表任何函数的对象,可以被保存和复制。
人浅解:
将其当做std::string 、std::vector<> 、这样的类型就可以了。只不过其值为函数指针,但比函数指针更灵活。
因为std::function 是一种模板,所以要传入类型,就如std::vector<int> 传入类型int一样
不过,std::function传入的是函数类型 返回值 (参数类型) 如:std::function<void (int)>
1. 可调用对象
可调用对象有一下几种定义:
- 是一个函数指针,参考 C++ 函数指针和函数类型;
- 是一个具有operator()成员函数的类的对象;
- 可被转换成函数指针的类对象;
- 一个类成员函数指针;
C++中可调用对象的虽然都有一个比较统一的操作形式,但是定义方法五花八门,这样就导致使用统一的方式保存可调用对象或者传递可调用对象时,会十分繁琐。C++11中提供了std::function和std::bind统一了可调用对象的各种操作。
不同类型可能具有相同的调用形式,如:
// 普通函数
int add(int a, int b){return a+b;}
// lambda表达式
auto mod = [](int a, int b){ return a % b;}
// 函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};
上述三种可调用对象虽然类型不同,但是共享了一种调用形式:
int(int ,int)
std::function就可以将上述类型保存起来,如下:
std::function<int(int ,int)> a = add;
std::function<int(int ,int)> b = mod ;
std::function<int(int ,int)> c = divide();
2. std::function
- std::function 是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。
- 定义格式:std::function<函数类型>。
- std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。
3. std::bind
可将std::bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:
- 将可调用对象和其参数绑定成一个防函数;
- 只绑定部分参数,减少可调用对象传入的参数。
3.1 std::bind绑定普通函数
double my_divide (double x, double y) {return x/y;}
auto fn_half = std::bind (my_divide,_1,2);
std::cout << fn_half(10) << '\n'; // 5
- bind的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。因此std::bind (my_divide,_1,2)等价于std::bind (&my_divide,_1,2);
- _1表示占位符,位于<functional>中,std::placeholders::_1;
3.2 std::bind绑定类的成员函数
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
int main()
{
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
f(5); // 100
}
- bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。
- 必须显示的指定&Foo::print_sum,因为编译器不会将对象的成员函数隐式转换成函数指针,所以必须在Foo::print_sum前添加&;
- 使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址 &foo;
3.3 参数绑定
bind其调用形式如下:
auto newCallable=bind(callable,arg_list);
bind的第一个参数为一个可调用对象,可调用对象是指可以对其使用调用运算符()的对象。
可调用对象常用的有函数、函数指针、重载了函数调用运算符的类和lambda表达式
arg_list是调用对象的参数列表,可以包含 _ 1, _ 2等这样的占位符,用于占据调用对象的参数位置,数字代表着是第几个未定的占位参数,占位符被定义在,命名空间placeholders中。也可以包含被绑定对象的参数。arg_list应该和被绑定对象的参数一样多。
int sum(int a, int b, int c) {
if (a > b)return a + c;
return b + c;
}
auto add = bind(sum, _1, _2, 10);
这样就将sum绑定由bind新生成的一个调用sum的对象上;
_ 1表示新对象中的第一个参数,是一个占位符。
也就是说,实际上,这个bind会add( _ 1, _ 2)会被映射成为sum( _ 1, _ 2, 10),此时add的参数就会代替原来的占位符成为调用sum的参数,当然前提是两者的类型要匹配。
比如:add(20,10)实际调用为sum(20,10,10),结果为30;
参数顺序可换
#include<bits/stdc++.h>
using namespace std;
using namespace placeholders;
int sum(int a, int b, int c)
{
if (a > b)return a + c;
return b + c;
}
int main(void)
{
auto add = bind(sum, _1, _2, 10);
auto add2 = bind(sum, _2, _1, 10);
int t = add(20, 10), t1 = add2(10, 20);
cout << t << " " << t1 << endl;
return 0;
}
bind也可以换原来参数的顺序,因为实际在调用新对象时,我们传递给新对象的参数实际就是那些占位符占据的位置的参数,所以上面调用情况如下:
add(20,10) 时,参数20对应占位符1,参数10对应占位符2,故实际调用为sum(20,10,10);
add2(10,20)时,参数10对应占位符1,参数20对应占位符2,故实际调用为sum(20,10,10);
从而重排了参数顺序。
原文链接:https://blog.csdn.net/Enterprise_/article/details/102806998
可以包含 _ 1, _ 2等这样的占位符,用于占据调用对象的参数位置,数字代表着是第几个未定的占位参数
void printValue(int value1, int value2, int value3)
{
cout << value1 << endl;
cout << value2 << endl;
cout << value3 << endl;
}
void testFunc(std::function<void(int, int)> printFunc, int value1, int value2)
{
printFunc(value1, value2);
}
int main()
{
auto newFunc1 = std::bind(printValue, placeholders::_1, placeholders::_2, 123);
testFunc(newFunc1, 111, 222); //111 222 123
auto newFunc2 = std::bind(printValue, placeholders::_1, 123, placeholders::_2);
testFunc(newFunc2, 111, 222);//111 123 222
system("pause");
}
3.3 绑定一个引用参数
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数希望以引用的方式传递,或是要绑定参数的类型无法拷贝。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <sstream>
using namespace std::placeholders;
using namespace std;
ostream & print(ostream &os, const string& s, char c)
{
os << s << c;
return os;
}
int main()
{
vector<string> words{"helo", "world", "this", "is", "C++11"};
ostringstream os;
char c = ' ';
for_each(words.begin(), words.end(),
[&os, c](const string & s){os << s << c;} );
cout << os.str() << endl;
ostringstream os1;
// ostream不能拷贝,若希望传递给bind一个对象,
// 而不拷贝它,就必须使用标准库提供的ref函数
for_each(words.begin(), words.end(),
bind(print, ref(os1), _1, c));
cout << os1.str() << endl;
}
4. 指向成员函数的指针
通过下面的例子,熟悉一下指向成员函数的指针的定义方法。
#include <iostream>
struct Foo {
int value;
void f() { std::cout << "f(" << this->value << ")\n"; }
void g() { std::cout << "g(" << this->value << ")\n"; }
};
void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
(foo1->*fun)(); // call fun on the object foo1
(foo2->*fun)(); // call fun on the object foo2
}
int main() {
Foo foo1{1};
Foo foo2{2};
apply(&foo1, &foo2, &Foo::f);
apply(&foo1, &foo2, &Foo::g);
}
- 成员函数指针的定义:void (Foo::*fun)(),调用是传递的实参: &Foo::f;
- fun为类成员函数指针,所以调用是要通过解引用的方式获取成员函数*fun,即
(foo1->*fun)();
参考
作者:georgeguo
链接:https://www.jianshu.com/p/f191e88dcc80
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
std::function 基本用法
std::function
是一个泛化的函数,它可以代表任何函数的对象,可以被保存和复制。本实验我们将练习 std::function
的定义和调用。
在 code1 目录下新建 code4.cpp 文件,编写下面这些代码:
#include <iostream>
#include <functional>
void test1(){std::cout<<"function"<<std::endl;}
int test2(int i){ return i; }
int test3(int i, int j){ return i+j; }
struct A{
void foo(int i){ std::cout<<i<<std::endl; }
};
int main() {
std::function<void()> fn1 = std::bind(test1);
std::function<int(int)> fn2 = std::bind(test2, std::placeholders::_1);
std::function<int(int, int)> fn3 = std::bind(test3, std::placeholders::_1, std::placeholders::_2);
std::function<int(int)> fn4 = std::bind(test3, 3, std::placeholders::_1);
std::function<int()> fn5 = std::bind(test3, 3, 4);
A a;
std::function<void(int)> fn6 = std::bind(&A::foo, &a, std::placeholders::_1);
fn1();
std::cout<<fn2(1)<<std::endl;
std::cout<<fn3(2, 3)<<std::endl;
std::cout<<fn4(3)<<std::endl;
std::cout<<fn5()<<std::endl;
fn6(8);
}
编译和运行代码:在 build 目录下执行
g++ ../code4.cpp -o code4 -std=c++11 && ./code4
输出结果:
function
1
5
6
7
8
以下面这个语句为例。
std::function<int(int)> fn2 = std::bind(test2, std::placeholders::_1)
std::function<int(int)>
表示的是一个返回类型为 int
,参数类型为 int
的函数,它的实例化是通过 std::bind
来实现的。注意这里的 std::placeholders::_1
表示的是第一个参数占位符,std::placeholders::_2
则表示的是第二个参数的占位符。参数占位符的目的是占位,在后面真正调用 std::function
的时候才填真正的参数。
如果不用占位符那么就需要在构造 std::function
的时候填充好参数,后面就不用再填充参数了,比如在如下语句中,
std::function<int()> fn5 = std::bind(test3, 3, 4)
bind test3
时,我们就直接把参数传进去了,后面调用的时候就不用再传参数了。
这里也要注意 bind 成员函数的用法,需要使用对象的指针,比普通函数多一个对象指针的参数。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)