热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

包装器和绑定器std::bind和std::function的回调技术原创

原标题:包装器和绑定器std::bind和std::function的回调技术原创回调函数回调函数就是一

原标题:包装器和绑定器std::bind和std::function的回调技术
原创


回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

std::function作为回调函数,std::function配合std::bind和lambda表达式能够很方便的指向函数指针。C++中有如下几种可调用对象:函数、函数指针、lambda表达式、bind对象、函数对象。其中,lambda表达式和bind对象是C++11标准中提出的(bind机制并不是新标准中首次提出,而是对旧版本中bind1st和bind2st的合并)。个人认为五种可调用对象中,函数和函数指针本质相同,而lambda表达式、bind对象及函数对象则异曲同工。


函数指针

插播一下函数指针和函数类型的区别:

函数指针指向的是函数而非对象。和其他指针类型一样,函数指针指向某种特定类型;
函数类型由它的返回值和参数类型决定,与函数名无关。
例如:

bool fun(int a, int b)

上述函数的函数类型是:bool(int, int)

上述函数的函数指针pf是:bool (*pf)(int, int)

一般对于函数来说,函数名即为函数指针

#include
//被调用的普通函数
int Func1(int x,int y){
std::cout< return x+y;
}
//有一个形参为函数指针
int Func2(int(*fp)(int,int),int x,int y){
return fp(x,y);
}
//定义一个函数指针类型为FType
typedef int (*FType)(int,int);
int Func3(FType fp,int x,int y){
return fp(x,y);
}
int main(){
Func2(Func1,100,100); //函数Func2调用函数Func1
Func3(Func1,200,200); //函数Func3调用函数Func1
return 0;
}

输出结果:

PS D:\bind和function的混合使用\build\函数指针\Debug> .\main.exe
200
400
PS D:\bind和function的混合使用\build\函数指针\Debug>

可以看出,函数指针作为参数,可以调用函数指针所指向的函数内容。


std::bind和std::function的回调技术

参考自《Linux多线程服务端编程》以及muduo源码,对其中的一些实现细节有着十分深刻的印象,尤其是使用std::bind和std::function的回调技术。可以说,这两个大杀器简直就是现代C++的“任督二脉”,甚至可以解决继承时的虚函数指代不清的问题。在此详细叙述使用std::bind和std::function在C++对象之间的用法,用以配合解决事件驱动的编程模型。

本文组成:

1.std::function

2.std::bind

3.使用std::bind和std::function的回调技术

4.std::bind绑定到虚函数时会表现出多态行为,解决继承时的虚函数指代不清的问题

function模板类和bind模板函数,使用它们可以实现类似函数指针的功能,但却却比函数指针更加灵活,特别是函数指向类 的非静态成员函数时。不过网上有文章简单测试:函数指针要比直接调用慢2s左右;std::function 要比函数指针慢2s左右 。


std::function

std::function位于头文件#include,可将各种可以调用实体进行封装统一。包括:


  • 普通函数

  • lambda表达式

  • 函数指针

  • 仿函数(函数对象)类重载了()

  • 类成员函数

  • 静态成员函数

实例通过上述几种方式实现一个简单的比较两个数的大小的功能(读者可拷贝代码观察结果)代码如下:

#include
#include
#include
std::unordered_map> functions;
std::function func;
//1.普通函数
bool compare_com(int a,int b){
return a>b;
}
//lambda表达式
auto compare_lam=[](int a,int b)->bool{
return a>b;
};
//仿函数(函数对象)
class Compare_class{
public:
bool operator()(int a,int b){
return a>b;
}
};
//类成员函数(静态或者动态)
class Compare{
public:
bool compare_member(int a,int b){
return a>b;
}
static bool compare_static_member(int a,int b){
return a>b;
}
};
void Func1(){
func=compare_com;
bool flag=func(10,1);
std::cout<<"普通函数输出,flag is "<}
void Func2(){
func=compare_lam;
bool flag=func(10,1);
std::cout<<"lambda表达式输出,flag is "<}
void Func3(){
func=Compare_class();
bool flag=func(10,1);
std::cout<<"仿函数(函数对象输出),flag is "<}
void Func4(){
func=Compare::compare_static_member;
bool flag=func(10,1);
std::cout<<"类静态成员函数输出,flag is "<}
void Func5(){
Compare tmp;
func=std::bind(&www.yii666.comamp;Compare::compare_member,tmp,std::placeholders::_1,std::placeholders::_2);
bool flag=func(10,1);
std::cout<<"类普通成员函数输出,flag is "<}
int main(){
Func1();
Func2();
Func3();
Func4();
Func5();
return 0;
}

输出结果:

普通函数输出,flag is 1
lambda表达式输出,flag is 1
仿函数(函数对象输出),flag is 1
类静态成员函数输出,flag is 1
类普通成员函数输出,flag is 1
PS D:\bind和function的混合使用\build\function\Debug>

由上文可以看出:由于可调用对象的定义方式比较多,但是函数的调用方式较为类似,因此需要使用一个统一的方式保存可调用对象或者传递可调用对象。于是,std::function就诞生了。

std::function是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。

定义function的一般形式:

# include
std::function<函数类型>

std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。

故而,std::function的作用可以归结于:


  • std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象,简化调用。

  • std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(如:函数指针这类可调用实体,是类型不安全的)。



std::bind

std::bind可以看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:


  • 将可调用对象和其参数绑定成一个仿函数;

  • 只绑定部分参数,减少可调用对象传入的参数。

  • 调用bind的一般形式:

auto newCallable = bind(callable, arg_list);

该形式表达的意思是:当调用newCallable时,会调用calla文章来源地址20890.htmlble,并传给它arg_list中的参数。

需要注意的是:arg_list中的参数可能包含形如_n的名字。其中n是一个整数,这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置:_ _ 1为newCallable的第一个参数,_ 2为第二个参数,以此类推。

std::bind函数将可调用对象(开头所述6类)和可调用对象的参数进行绑定,返回新的可调用对象(std::function类型,参数列表可能改变),返回的新的std::function可调用对象的参数列表根据bind函数实参中std::placeholders::_x从小到大对应的参数确定。

实例详细说明返回的新的std::function可调用对象的参数列表如何确定:

#include
#include
struct Int{
int a;
};
bool compare_com(struct Int a,float b){
return a.a>b;
}
void Func1(){
Int a{3};
//Int a={3};
//placeholders::_1对应float,placeholders::_2对应struct Int所以返回值func返回值得类型为function

std::function func=std::bind(compare_com,std::placeholders::_2,std::placeholders::_1);
bool flag=func(2.0,a);
std::cout<<"flag is "<}
int main(){
Func1();
return 0;
}

输出结果:

flag is 1
PS D:\bind和fu文章来源站点https://www.yii666.com/nction的混合使用\build\bind\Debug>

由此例子可以看出:


  • 预绑定的参数是以值传递的形式,不预绑定的参数要用std::placeholders(占位符)的形式占位,从_1开始,依次递增,是以引用传递的形式;

  • std::placeholders表示新的可调用对象的第几个参数,而且与原函数的该占位符所在位置的进行匹配;

  • **bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址,这是因为对象的成员函数需要有this指针。**并且编译器不会将对象的成员函数隐式转换成函数指针,需要通过&手动转换;一看到bind里面绑定的类成员函数时都带着地址,我非常不理解,现在根据这句话查到了原来对于非类成员函数,编译器会把函数名隐式转换成指针的形式,即转换成函数指针,而对于类成员函数无法转换。

  • 并且编译器不会将对象的成员函数隐式转换成函数指针,需要通过&手动转换",一看到bind里面绑定的类成员函数时都带着地址,我非常不理解,现在根据这句话查到了原来对于非类成员函数,编译器会把函数名隐式转换成指针的形式,即转换成函数指针,而对于类成员函数无法转换。

std::bind的返回值是可调用实体,可以直接赋给std::function。


使用std::bind和std::function的回调技术

C++的类成员函数不能像普通函数那样用于回调,因为每个成员函数都需要有一个对象实例去调用它。

通常情况下,要实现成员函数作为回调函数:

**一种过去常用的方法就是把该成员函数设计为静态成员函数(因为类的成员函数需要隐含的this指针 而回调函数没有办法提供)。**但这样做有一个缺点,就是会破坏类的结构性,因为静态成员函数只能访问该类的静态成员变量和静态成员函数,不能访问非静态的。

要解决这个问题,可以把对象实例的指针或引用做为参数传给它。后面就可以靠这个对象实例的指针或引用访问非静态成员函数。

下面的所有讨论基于对象。

代码如下:

#include
#include
typedef std::function func;
class Blas{
public:
//类成员函数
virtual void add(int a,int b){
std::cout< }
//类静态成员函数
static void addStatic(int a,int b){
std::cout< }
};
class BBlas:public Blas{
public:
virtual void add(int a,int b){
std::cout< }
};
void Func1(){
//Blas blas
BBlas bblas;
//使用std::bind绑定类静态成员函数
func f1(std::bind(&Blas::addStatic,1,2));
f1();
//使用std::bind绑定类的成员函数 对象加地址与不加地址实现结果一样
//func f2(std::bind(&Blas::add,&bblas,1,2));
func f2(std::bind(&Blas::add,bblas,1,2));
f2();
}
int main(){
Func1();
return 0;
}

输出结果:

3this Blas
3this BBlas
PS D:\bind和function的混合使用\build\bind和function混合\Debug>

上述代码中的区别是:如果不是类的静态成员函数,需要在参数绑定时,往绑定的参数列表中加入使用的对象。

另一种办法就是使用std::bind和std::function结合实现回调技术。

#include
#include
typedef std::function func;
class Blas{
public:
void SetCallBack(const func& cb){
cb_=cb;
}
void PrintFunctor(){
cb_();
}
private:
func cb_;
};
class Atlas{
public:
Atlas(int x)
:x_(x){
//使用当前类的静态成员函数
blas_.SetCallBack(std::bind(&AddStatic,x,2));
//使用当前类的非静态成员函数
blas_.SetCallBack(std::bind(&Atlas::Add,this,x,2));
}
void Print(){
blas_.PrintFunctor();
}
private:
void Add(int a,int b){
std::cout< }
static void AddStatic(int a,int b){
std::cout< }
private:
Blas blas_;
int x_;
};
void Func1(){
Atlas atlas(5);
atlas.Print();
}
int main(){
Func1();
return 0;
}

输出结果:

7

两个函数在Atlas类中,并且可以自由操作Atlas的数据成员。尽管是将add()系列的函数封装成函数对象传入Blas中,并且在Blas类中调用,但是它们仍然具有操作Atlas数据成员的功能,在两个类之间形成了弱的耦合作用。但是如果要在两个类之间形成弱的耦合作用,必须在使用std::bind()封装时,向其中传入this指针。


可调用对象

在 C++中, 可以像函数一样调用的有: **普通函数、类的静态成员函数、仿函数、 lambda 函数、类的非静态成员函数、 可被转换为函数的类的对象,**统称可调用对象或函数对象。

可调用对象有类型, 可以用指针存储它们的地址, 可以被引用(类的成员函数除外) 。


普通函数

普通函数类型可以声明函数、 定义函数指针和函数引用, 但是, 不能定义函数的实体。

#include
#include
using Func=void(int,const std::string&); //普通函数的别名
Func Show; //声明普通函数
//void Show(int,const std::string&);
//定义普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的"<}
int main(){
Show(1,"我是一只笨鸟"); //直接调用普通函数
void(*fp1)(int,const std::string&)=Show; //声明函数指针,指向Show这个函数
void(&fr1)(int,const std::string&)=Show; //声明函数引用,引用Show这个函数
fp1(2,"我是一只笨鸟"); //用函数指针调用Show普通函数
fr1(3,"我是一只笨鸟"); //用函数引用调用普通函数
Func* fp2=Show; //声明函数指针,指向Show普通函数
Func& fr2=Show; //声明函数引用,指向Show普通函数
fp2(4,"我是一只笨鸟"); //用声明的函数指针调用普通函数
fr2(5,"我是一只笨鸟"); //用声明的函数引用调用普通函数
return 0;
}

输出结果:

PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的1, 我是一只笨鸟
亲爱的2, 我是一只笨鸟
亲爱的3, 我是一只笨鸟
亲爱的4, 我是一只笨鸟
亲爱的5, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>


类的静态成员函数

类的静态成员函数和普通函数本质上是一样的,把普通函数放在类中而已。

#include
#include
using Func=void(int ,const std::string& ); //普通函数起别名
struct AA{
//类中有静态成员函数
static void Show(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
int main(){
AA::Show(1,"我是一只笨鸟 "); //直接用类::调用静态成员函数
void(*fp1)(int,const std::string&)=AA::Show; //用函数指针指向静态成员函数
void(&fr1)(int,const std::string&)=AA::Show; //用函数引用引用静态成员函数
fp1(2,"我是一只笨鸟 "); //用函数指针调用静态成员函数
fr1(3,"我是一只笨鸟 "); //用函数引用调用静态成员函数
Func* fp2=AA::Show; //定义一个函数指针指向静态成员函数
Func& fr2=AA::Show; //定义一个函数引用指向静态成员函数
fp2(4,"我是一只笨鸟 "); //用函数指针调用静态成员函数
fr2(5,"我是一只笨鸟 "); //用函数引用调用静态成员函数
return 0;
}

输出结果:

亲爱的 1, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 3, 我是一只笨鸟
亲爱的 4, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug> ^C
PS D:\可调用对象、包装器function、绑定器bind\Debug>


仿函数(函数对象)

仿函数的本质是类,调用的代码像函数。仿函数的类型就是类的类型。

#include
#include
struct BB{
void operator()(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
int main(){
BB bb;
bb(11,"我是一只笨鸟 "); //用对象调用仿函数(函数对象)
BB()(12,"我是一只笨鸟 "); //用匿名函数对象调用仿函数(函数对象)
BB& br=bb; //对象的引用绑定bb对象
br(13,"我是一只笨鸟 "); //用对象的引用调用仿函数
return 0;
}

输出结果:

PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 11, 我是一只笨鸟
亲爱的 12, 我是一只笨鸟
亲爱的 13, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>


lambda表达式

lambda 函数的本质是仿函数, 仿函数的本质是类。

#include
#include
int main(){
//创建lambda对象
auto lb=[](int n,const std::string& str)->void{
std::cout<<"亲爱的 "< };
auto& lr=lb; //引用lambda对象
lb(1,"我是一只笨鸟 "); //文章来源地址20890.html直接用lambda对象调用仿函数
lr(2,"我是一只笨鸟 "); //用lambda对象的引用调用仿函数
return 0;
}

输出结果:

PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 1 ,我是一只笨鸟
亲爱的 2 ,我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>


类的非静态成员函数

类的非静态成员函数有地址,但是,只能通过类的对象才能调用它,所以, C++对它做了特别处理。

类的非静态成员函数只有指针类型,没有引用类型,不能引用。

#include
#include
struct CC{
//类中有普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
int main(){
CC cc;
cc.Show(14,"我是一只笨鸟 "); //直接用类的对象去调用类中方法
void(CC::*fp1)(int,const std::string&)=&CC::Show; //定义类的成员函数指针指向类的成员函数
(cc.*fp1)(15,"我是一只笨鸟 "); //用类的成员函数指针调用成员函数
using pFunc=void(CC::*)(int,const std::string&); //类成员函数指针起别名
pFunc fp2=&CC::Show; //类成员函数指针指向类成员函数
(cc.*fp2)(16,"我是一只笨鸟 ");
//cc.*fp2就是解引用出这个函数出来,得到这个函数
return 0;
}

输出结果:

PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 14, 我是一只笨鸟
亲爱的 15, 我是一只笨鸟
亲爱的 16, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>


可被转换为函数指针的类对象

类可以重载类型转换运算符 operator 数据类型() ,如果数据类型是函数指针或函数引用类型,那么该类实例也将成为可调用对象。

它的本质是类,调用的代码像函数。

在实际开发中,意义不大。

#include
#include
//定义函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<}
struct DD{
//可以被转换成函数指针的类
using Func=void(*)(int,const std::string&);
operator Func(){
return Show;
}
};
int main(){
DD dd;
dd(17,"我是一只笨鸟 ");
return 0;
}

输出结果:

PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 17 ,我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>


包装器function

std::function 模板类是一个通用的可调用对象的包装器,用简单的、 统一的方式处理可调用对象。
template
class function……
Fty 是可调用对象的类型,格式: 返回类型(参数列表)。
包含头文件: #include
注意:
⚫ 重载了 bool 运算符,用于判断是否包装了可调用对象。
⚫ 如果 std::function 对象未包装可调用对象, 使用 std::function 对象将抛出 std::bad_function

call 异常。

#include
#include
#include
//普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<}
struct AA{
//类中静态成员函数
static void Show(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
struct BB{
//类中仿函数
void operator()(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
struct CC{
//类的普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
struct DD{
//类中可以被转换普通函数的类
using Func=void(*)(int,const std::string& );
operator Func(){
return Show; //返回普通函数的地址
}
};
int main(){
using Func=void(int,const std::string& str); //函数类型的别名
//普通函数
void(*fp1)(int,const std::string&)=Show; //声明函数指针,指向函数对象
fp1(1,"我是一只笨鸟 "); //用函数指针调用普通函数
std::function fn1=Show; //定义function对象包装普通函数Show
fn1(1,"我是一只笨鸟"); //用function对象调用普通函数
//类中静态成员函数
void(*fp2)(int,const std::string&)=AA::Show; //声明函数指针指向类中成员函数对象
fp2(2,"我是一只笨鸟 "); //用函数指针调用类中成员函数
std::function fn2=AA::Show; //定义function对象包装类中静态成员函数
fn2(2,"我是一只笨鸟"); //用function对象调用类中静态成员函数
//仿函数
BB bb;
bb(3,"我是一只笨鸟 "); //用仿函数对象调用仿函数
BB()(3,"我是一只笨鸟"); //仿函数匿名对象调用
std::function fn3=BB(); //定义function对象包装仿函数
fn3(3,"我是一只笨鸟 "); //用function对象调用仿函数
//lambad表达式
auto lb=[](int n,const std::string& str)->void{ //调用lambad表达式
std::cout<<"亲爱的 "< };
lb(4,"我是一只笨鸟 ");
std::function fn4=lb;
std::function fn5=[](int n,const std::string& str)->void{
std::cout<<"亲爱的 "< };
fn5(4,"我是一只笨鸟 ");
//类中非静态成员函数 注意这里包装的时候要加对象的地址
CC cc;
void(CC::*fp3)(int,const std::string&)=&CC::Show; //定义类成员函数指针指向类成员函数
(cc.*fp3)(5,"我是一只笨鸟 "); //用类成员函数指针调用类成员函数
std::function fn6=&CC::Show; //定义function对象包装成员函数
fn6(cc,5,"我是一只笨鸟 "); //用function对象调用成员函数
//可以被转换为函数指针的对象
DD dd;
dd(6,"我是一只笨鸟 "); //用来被转换为函数指针的类对象的普通函数
std::function fn7=dd; //定义function对象包装可以被转换为函数指针的类
fn7(6,"我是一只笨鸟 ");

std::function fn8;
try{
fn8(7,"我是一只笨鸟 ");
}catch(std::bad_function_call e){
std::cout<<"抛出了 std::bad_function_call异常";
}
return 0;
}

输出结果:

PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
亲爱的 1, 我是一只笨鸟
亲爱的 1, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 3 ,我是一只笨鸟
亲爱的 3 ,我是一只笨鸟
亲爱的 3 ,我是一只笨鸟
亲爱的 4, 我是一只笨鸟
亲爱的 4, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
亲爱的 6, 我是一只笨鸟
亲爱的 6, 我是一只笨鸟
抛出了 std::bad_function_call异常
PS D:\可调用对象、包装器function、绑定器bind\Debug>


适配器(绑定器)bind

std::bind()模板函数是一个通用的函数适配器(绑定器) ,它用一个可调用对象及其参数,生成一个新的可调用对象,以适应模板。包含头文件: #include
函数原型:
template
function<> bind (Fx&& fx, Args&…args);
**Fx:**需要绑定的可调用对象(可以是前两节课介绍的那六种,也可以是 function 对象) 。
**args:**绑定参数列表, 可以是左值、 右值和参数占位符 std::placeholders::_n,如果参数不是占位符,缺省为值传递, std:: ref(参数)则为引用传递。
std::bind()返回 std::function 的对象。
std::bind()的本质是仿函数。

#include
#include
#include
// void Show(int n,const std::string& str){
// std::cout<<"亲爱的 "<// }
//普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "<}
struct AA{
//类中静态成员函数
static void Show(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
struct BB{
//类中仿函数
void operator()(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
struct CC{
//类的普通函数
void Show(int n,const std::string& str){
std::cout<<"亲爱的 "< }
};
struct DD{
//类中可以被转换普通函数的类
using Func=void(*)(int,const std::string& );
operator Func(){
return Show; //返回普通函数的地址
}
};
int main(){
//std::bind返回的function模板对象,所有可以用auto fn1=Show接收之类的
//两种输出结果是一样的
std::function fn1=Show;
std::function fn2=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
fn1(1,"我是一只笨鸟 ");
fn2(1,"我是一只笨鸟 ");
//当现有的函数类型Show与要求的函数类型不一样
//意思是现有函数类型参数顺序不一样或者要求的函数类型数量少
//这个时候就要用到std::bind绑定器(适配器),
//用std::bind对现有函数对象进行转换,生成新的函数对象,与要求的函数对象类型匹配上
//如果不用std::bind(适配器)的话,要实现需求的话就只能重载原来的函数达到要求,比较麻烦
// std::function fn3=Show;
// std::function fn3=Show;
std::functionfn3=std::bind(Show,std::placeholders::_2,std::placeholders::_1);
fn3("我是一只笨鸟 ",2);
//直接提前绑定编号3,发行对象只有一个参数对应一个std::placeholders::_1一个占位符
std::function fn4=std::bind(Show,3,std::placeholders::_1);
fn4("我是一只笨鸟 ");

int n=4;
//用std::bind()绑定的参数缺省的话是之值传递,用传引用的话用std::ref(n)
std::functionfn5=std::bind(Show,std::ref(n),std::placeholders::_1);
n=100;
fn5("我是一只笨鸟 ");
//要发行的对象参数比原来的函数对象多,std::bind绑定的时候不用管多余的参数,调用的时候随便加什么就行
std::function fn6=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
fn6(5,"我是一只笨鸟",6);
//普通函数
std::function fn7=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
fn7(6,"我是一只笨鸟 ");

//类的静态成员函数
std::function fn8=std::bind(AA::Show,std::placeholders::_1,std::placeholders::_2);
fn8(7,"我是一只笨鸟 ");
//仿函数(函数对象)
std::function fn9=std::bind(BB(),std::placeholders::_1,std::placeholders::_2);
fn9(8,"我是一只笨鸟 ");
//lambda表达式
std::function fn10=std::bind([](int n,const std::string& str)->void{
std::cout<<"亲爱的 "< },std::placeholders::_1,std::placeholders::_2);
fn10(9,"我是一只笨鸟 ");
//类的非静态成员函数
CC cc;
std::function fn11=std::bind(&CC::Show,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3);
fn11(cc,10,"我是一只笨鸟 ");
//在实际开发中,对于类的非静态成员函数,发行对象调用的时候不希望把对象传过去,不适合模板
// 为了解决这个问题,可以把对象提前绑定
std::function fn12=std::bind(&CC::Show,&cc,std::placeholders::_1,std::placeholders::_2);
fn12(10,"我是一只笨鸟 ");
//可以被转换为函数指针的类对象
DD dd;
std::function fn13=std::bind(dd,std::placeholders::_1,std::placeholders::_2);
fn13(11,"我是一只笨鸟 ");

return 0;
}

输出结果:

亲爱的 1, 我是一只笨鸟
亲爱的 1, 我是一只笨鸟
亲爱的 2, 我是一只笨鸟
亲爱的 3, 我是一只笨鸟
亲爱的 100, 我是一只笨鸟
亲爱的 5, 我是一只笨鸟
亲爱的 6, 我是一只笨鸟
亲爱的 7, 我是一只笨鸟
亲爱的 8 ,我是一只笨鸟
亲爱的 9, 我是一只笨鸟
亲爱的 10, 我是一只笨鸟
亲爱的 10, 我是一只笨鸟
亲爱的 11, 我是一只笨鸟
PS D:\可调用对象、包装器function、绑定器bind\Debug>

在这里插入图片描述


可变函数和参数

写一个函数,函数的参数是函数对象及参数, 功能和 thread 类的构造函数相同。

thread类的构造函数可以接收不同的对象以及对象不同的参数。

#include
#include
#include
void Show1(){
//无参普通函数
std::cout<<"亲爱的,我是一只笨鸟 "<}
void Show2(const std::string& str){
//带一个参数的普通函数
std::cout<<"亲爱的, "<}
struct CC{
void Show3(int n,const std::string& str){
//类中带两个参数的普通函数,非静态成员函数
std::cout<<"亲爱的 "< }
};
template
// void Show(Fn fn,Args...args){
//C++11一般要把返回类型写在后面
auto Show(Fn&& fn,Args&&...args)->decltype(std::bind(std::forward(fn),std::forward(args)...)){
std::cout<<"开始 "< //auto f=std::bind(fn,args...);
auto f=std::bind(std::forward(fn),std::forward(args)...);
f();
std::cout<<"结束 "< return f;
}
int main(){
std::cout<<"================"< Show(Show1);
auto f1=std::bind(Show1);
f1();
Show(Show2,"我是一只笨鸟 ");
std::function f2=std::bind(Show2,std::placeholders::_1);
f2("我是一只笨鸟 ");
CC cc;
Show(&CC::Show3,&cc,3,"我是一只笨鸟 ");
std::function f3=std::bind(&CC::Show3,&cc,std::placeholders::_1,std::placeholders::_2);
f3(3,"我是一只笨鸟 ");
std::cout<<"================"< //std::thread t1();
// std::thread t2(Show1);
// std::thread t3(Show2,"我是一只笨鸟 ");
// CC cc;
// std::thread t4(&CC::Show3,&cc,3,"我是一只笨鸟 ");
// //t1.join();
// t2.join();
// t3.join();
// t4.join();
return 0;
}

输出结果:

PS D:\可调用对象、包装器function、绑定器bind\Debug> .\main.exe
================
开始
亲爱的,我是一只笨鸟
结束
亲爱的,我是一只笨鸟
开始
亲爱的, 我是一只笨鸟
结束
亲爱的, 我是一只笨鸟
开始
亲爱的 3, 我是一只笨鸟
结束
亲爱的 3, 我是一只笨鸟
================
PS D:\可调用对象、包装器function、绑定器bind\Debug>


回调函数的实现

如何取代虚函数

map和unorder_map

#include
#include
#include
/**
*
* map概念总结:
* map的用法和python中的字典用法是类似的。
* map是关联式容器,按照特定的顺序存储由key和value值组成的键值对。
* key:常用于对元素进行排序的唯一标识,元素总是按照其key进行排序的。
* 自动建立key-value的对应。key和value可以是任意的数据类型。
* map容器中没有两个元素具有相同的key。
*
* template
pair insert(value_type&& _Val) {
const auto _Result = _Emplace(_STD move(_Val));
return {iterator(_Result.first, _Get_scary()), _Result.second};
}
_NODISCARD iterator find(const key_type& _Keyval) {
return iterator(_Find(_Keyval), _Get_scary());
}
删除某个迭代器位置
template = 0>
iterator erase(iterator _Where) noexcept {
const auto _Scary = _Get_scary();
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Where._Getcont() == _Scary, "map/set erase iterator from incorrect container");
_STL_VERIFY(!_Where._Ptr->_Isnil, "cannot erase map/set end() iterator");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return iterator(_Erase_unchecked(_Where._Unwrapped()), _Scary);
}
删除某个迭代器中位置
iterator erase(const_iterator _Where) noexcept {
const auto _Scary = _Get_scary();
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Where._Getcont() == _Scary, "map/set erase iterator from incorrect container");
_STL_VERIFY(!_Where._Ptr-www.yii666.com>_Isnil, "cannot erase map/set end() iterator");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return iterator(_Erase_unchecked(_Where._Unwrapped()), _Scary);
}
删除迭代器某个区间
iterator erase(const_iterator _First, const_iterator _Last) noexcept {
return iterator(_Erase_unchecked(_First._Unwrapped(), _Last._Unwrapped()), _Get_scary());
}
删除key
size_type erase(const key_type& _Keyval) noexcept(noexcept(_Eqrange(_Keyval))) {
const auto _Where = _Eqrange(_Keyval);
const _Unchecked_const_iterator _First(_Where.first, nullptr);
const _Unchecked_const_iterator _Last(_Where.second, nullptr);
const auto _Num = static_cast(_STD distance(_First, _Last));
_Erase_unchecked(_First, _Last);
return _Num;
}

*/
void Func1(){
//map中的插入元素
std::map person;
person[0]="tom";
person.insert(std::pair(1,"jery"));
person.insert(std::make_pair(3,"rose"));
person.insert(std::map::value_type(4,"Speike"));
std::pair p1(5,"mary");
person.insert(p1);
//map中的遍历元素方法1
std::map::iterator iter1;
for(iter1=person.begin();iter1!=person.end();iter1++){
std::cout<first<<" "<second< }
//map中的遍历元素方法2
for(auto iter2=person.begin();iter2!=person.end();iter2++){
std::cout<first<<" "<second< }
//map中的遍历元素方法3
for(auto iter3:person){
std::cout<<(iter3).first<<" "<<(iter3).second< }
//map的insert方法插入是否成功,insert插入的元素返回值是std::pari
std::pair::iterator,bool> insert_pair;
insert_pair=person.insert(std::map::value_type(6,"kuku"));
std::cout<<"是否插入成功: "< //获取map中的元素find(key),返回值是map迭代器
std::map::iterator iter4;
iter4=person.find(6); //6是key值
if(iter4!=person.end()){
std::cout<first<<" "<second< }else{
std::cout<<"not find"< }
//删除map中的元素,erase(key),或者erase(iterator)两种方法,先用find(key)返回元素的迭代器,在调用erase(iterator)删除
std::cout<<"======================="< auto iter5=person.find(5);
person.erase(iter5);
person.erase(6);
for(auto iter6:person){
std::cout<<(iter6).first<<" "<<(iter6).second< }
std::cout<<"======================="< //自动建立key-value的对应。key和value可以是任意的数据类型。这里改成std::string,std::string
std::map fruits;
fruits["apple"]="苹果";
fruits.insert(std::pair("banana","香蕉"));
fruits.insert(std::map::value_type("pear","梨"));
std::map::iterator iter7=fruits.find("banana");
if(iter7!=fruits.end()){
std::cout<first<<" "<second< }else{
std::cout<<"not find"< }
std::cout<<"========================="< fruits.erase(iter7);
for(auto iter8:fruits){
std::cout<<(iter8).first<<" "<<(iter8).second< }
}
int main(){
Func1();
return 0;
}

来源于:包装器和绑定器std::bind和std::function的回调技术
原创


推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
author-avatar
小文982_412
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有