一、函数指针的衍生
在前面分析讨论了函数指针的问题,基本也明白了函数指针的工作的整个流程。既然函数指针应用如此方便,STL库一定会对进行封装。这也是经常看到的std::function,std::bind,std::mem_fn这几个模板类和函数模板。那么它们几个主要作用是什么呢?这个在官方文档说的很清楚。std::mem_fn用在类成员函数,你看它的名字mem就明白了,如果使用全局的,反而是无法通过的。那既然std::bind这也能做到这个功能,为啥还要弄个std::mem_fn呢?原因就是为了简单。就象工具一样,既有普遍适用的工具,也有专用的工具。专用工具在特定场合下更方便更顺手。
而std::function则是一个类模板,它是一个通用多态函数包装器。 std::function 的实例能存储、复制及调用任何可复制构造 (CopyConstructible) 的可调用 (Callable) 目标——函数、 lambda 表达式、 bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
这下明白了吧,std::function是结果,而其它两个是实现这个结果的手段。其中一个手段比另外一个手段更专一,在专一的应用场景下,更方便简单。
二、std::bind和std::function
这里首先要介绍一下std::function的应用场景:
1、它可以用于普通函数
其中也包括静态和普通全局函数。
2、可以用于Lambda表达式
这个对模板的Lambda表达式也支持
3、可以用于类成员函数
又可以分为静态成员和普通类成员函数两种
4、可以用于模板函数
当然也包括模板类成员函数和普通模板函数
5、仿函数
就是对小括号的重载。
下面看一个完整的例子:
#include <functional>
#include <iostream>
void DisplayGreet()
{
std::cout << "Global :Hello, world! "<<std::endl;
}
int DisplayNumber(int i)
{
std::cout << "Global :number: " << i << std::endl;
return 0;
}
template<typename T,typename N>
void DisplayTemplate(T t, N n)
{
std::cout << "call DisplayTemplage:" << t + n << std::endl;
}
template <typename T = int>
struct FuncExample
{
template <typename T>
T FuncGet(T i)
{
std::cout << "call template class :" << i + 10 << std::endl;
return i;
}
template <typename T>
static T FuncSet(T i)
{
std::cout << "call template class :" << i + 1 << std::endl;
return i;
}
T operator()(T i,T j)
{
std::cout << "call functor:" <<i+j<< std::endl;
return i + j;
}
};
struct Foo {
void display_greeting() {
std::cout << "Hello, world.\n";
}
void display_number(int i) {
std::cout << "number: " << i << '\n';
}
static int display_sdata(int i)
{
std::cout << "static function :" << i << std::endl;
return 0;
}
int operator()(int i)
{
std::cout << "call functor non template:" << i << std::endl;
return i;
}
int data = 7;
};
void TestFunc()
{
//普通函数分别直接使用和绑定两种方式
using namespace std::placeholders;
std::function<void()> f1 = std::bind(DisplayGreet);
f1();
std::function<void()> f2 = DisplayGreet;
f2();
std::function<int(int)> f3 = std::bind(DisplayNumber, _1);
f3(5);
std::function<int(int)> f4 = DisplayNumber;
f4(6);
//类成员函数:普通和静态两种
Foo f;
std::function<void(int)> fc1 = std::bind(&Foo::display_number, &f, _1);
fc1(5);
std::function<int(int)> fs1 = Foo::display_sdata;
fs1(6);
std::function<int(int)> fs2 = &Foo::display_sdata;
fs2(6);
std::function<int(int)> fs3 = std::bind(Foo::display_sdata,_1);
fs3(6);
//Lambda表达式
std::function<int(int)> fl = [&](int i)->int {
std::cout << "this is lambda:"<<i << std::endl;
return 0;
};
fl(3);
std::function<int(int,int)> flt = []<typename T>(T x, T y)->T { return x + y; };
auto x = []<typename T>(T x, T y)->T { return x + y; };
std::cout<<"lambda template" << flt(1, 2)<< " call x:"<< x(3,4)<<std::endl;
//模板
std::function<void(int, float)> ft1 = DisplayTemplate<int,float>;
ft1(100,1.0f);
FuncExample<int> fee;
std::function<int(int)> ft2 = std::bind(&FuncExample<int>::FuncGet<int>,&fee,_1);
ft2(6);
std::function<int(int)> ft3 = std::bind(&FuncExample<int>::FuncSet<int>, _1);
ft3(6);
//绑定仿函数
FuncExample<int> fe;//此处用来对比正常使用
fe(1, 1);
std::function<int(int, int)> ftor = FuncExample<int>();
ftor(1,2);
std::function<int(int)> ftor1 = Foo();
ftor1(3);
}
int main()
{
TestFunc();
return 0;
}
这个运行结果没啥意义,就不贴上来了。
包括从下一小节的代码来看std::bind也是十分灵活的。基本上来说,把目前常用的使用方式都列举了过来,有兴趣的可以自己把代码拷到程序里跑一下,当然要支持c++20的编译器,因为里有Lambda模板。有一些相同类似的用法,都可以在几种方式种使用,这里可能有的地方就没有重写。但看到有的代码可以使用,其它都可以使用。比如std::bind绑定里可以不写类的对象实例,但是就需要在后面的使用函数中输入(看std::mem_fn中的bind)。
三、std::mem_fn
此函数模板应用比较简单,不用处理相关的参数信息,看一下具体的例程:
#include <functional>
#include <iostream>
struct Foo {
void display_greeting() {
std::cout << "Hello, world.\n";
}
void display_number(int i) {
std::cout << "number: " << i << '\n';
}
int data = 7;
};
int main()
{
using namespace std::placeholders;
Foo f;
//std::mem_fn
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';
//std::bind ---1
auto greetb = std::bind(&Foo::display_greeting, _1);
greetb(&f);
auto print_numb = std::bind(&Foo::display_number, _1, _2);
print_numb(&f, 42);
auto access_datab = std::bind(&Foo::data,_1);
std::cout << "data: " << access_datab(&f) << '\n';
//std::bind ---2
auto greetb1 = std::bind(&Foo::display_greeting, &f);
greetb1();
auto print_numb1 = std::bind(&Foo::display_number, &f,_1);
print_numb1(42);
auto access_datab1 = std::bind(&Foo::data, &f);
std::cout << "data: " << access_datab1() << '\n';
将其和std::bind对比一下就会发现,它的应用要简单很多。参数越多,越可以表现出来。当然,谁也不会没事写太多参数。但是,能省一点是一点吧。
四、总结
很多的技术和技巧,需要经常用。台上一分钟,台下十年功。编程也是如此,真正的天才惊艳的开发者,少而又少。大多数还是靠不断的积累知识,不断思考总结,不断的迭代重构代码,在这其中不断的成长。然后才能写出不断优秀的代码来。只想靠看看技术文章就想着某天能成为一个开发高手的人,基本上都是在浪费光阴,不动手亲自去编程开发,再好的技术也是一种样子,实践的过程就是验证对技术掌握的深度。想当然的认为掌握了,并不是真的掌握了。
越是学习,越是发现自己懂得太少,战战兢兢,如履薄冰。一起努力!