包装器和绑定器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<iostream>

//被调用的普通函数
int Func1(int x,int y){
    std::cout<<x+y<<std::endl;
    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<iostream>
#include<functional>
#include<unordered_map>

std::unordered_map<int,std::function<void(int,int)>> functions;

std::function<bool(int,int)> 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 "<<flag<<std::endl;
}

void Func2(){
    func=compare_lam;
    bool flag=func(10,1);
    std::cout<<"lambda表达式输出,flag is "<<flag<<std::endl;
}

void Func3(){
    func=Compare_class();
    bool flag=func(10,1);
    std::cout<<"仿函数(函数对象输出),flag is "<<flag<<std::endl;
}

void Func4(){
    func=Compare::compare_static_member;
    bool flag=func(10,1);
    std::cout<<"类静态成员函数输出,flag is "<<flag<<std::endl;
}

void Func5(){
    Compare tmp;
    func=std::bind(&Compare::compare_member,tmp,std::placeholders::_1,std::placeholders::_2);
    bool flag=func(10,1);
    std::cout<<"类普通成员函数输出,flag is "<<flag<<std::endl;
}

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 <functional>
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时,会调用callable,并传给它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<iostream>
#include<functional>

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<bool(float,Int)>
    
    std::function<bool(float,struct Int)> func=std::bind(compare_com,std::placeholders::_2,std::placeholders::_1);
    bool flag=func(2.0,a);
    std::cout<<"flag is "<<flag<<std::endl;
}

int main(){
    Func1();
    return 0;
}

输出结果:

flag is 1
PS D:\bind和function的混合使用\build\bind\Debug> 

由此例子可以看出:

  • 预绑定的参数是以值传递的形式,不预绑定的参数要用std::placeholders(占位符)的形式占位,从_1开始,依次递增,是以引用传递的形式;
  • std::placeholders表示新的可调用对象的第几个参数,而且与原函数的该占位符所在位置的进行匹配;
  • **bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址,这是因为对象的成员函数需要有this指针。**并且编译器不会将对象的成员函数隐式转换成函数指针,需要通过&手动转换;一看到bind里面绑定的类成员函数时都带着地址,我非常不理解,现在根据这句话查到了原来对于非类成员函数,编译器会把函数名隐式转换成指针的形式,即转换成函数指针,而对于类成员函数无法转换。
  • 并且编译器不会将对象的成员函数隐式转换成函数指针,需要通过&手动转换",一看到bind里面绑定的类成员函数时都带着地址,我非常不理解,现在根据这句话查到了原来对于非类成员函数,编译器会把函数名隐式转换成指针的形式,即转换成函数指针,而对于类成员函数无法转换。

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

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

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

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

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

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

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

代码如下:

#include<iostream>
#include<functional>

typedef std::function<void()> func;

class Blas{
public:
    //类成员函数
    virtual void add(int a,int b){
        std::cout<<a+b<<std::endl;
    }
    //类静态成员函数
    static void addStatic(int a,int b){
        std::cout<<a+b<<"this Blas"<<std::endl;
    }
};

class BBlas:public Blas{
public:
    virtual void add(int a,int b){
        std::cout<<a+b<<"this BBlas"<<std::endl;
    }
};

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<iostream>
#include<functional>

typedef std::function<void()> 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<<a+b<<std::endl;
    }
    static void AddStatic(int a,int b){
        std::cout<<a+b<<std::endl;
    }
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<iostream>
#include<string>

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<<"亲爱的"<<n<<", "<<str<<std::endl;
}

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<iostream>
#include<string>

using Func=void(int ,const std::string& );    //普通函数起别名

struct AA{
    //类中有静态成员函数
    static void Show(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

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<iostream>
#include<string>

struct BB{
    void operator()(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

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<iostream>
#include<string>

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

输出结果:

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

类的非静态成员函数

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

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

#include<iostream>
#include<string>

struct CC{      
    //类中有普通函数
    void Show(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

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<iostream>
#include<string>

//定义函数
void Show(int n,const std::string& str){
    std::cout<<"亲爱的 "<<n<<" ,"<<str<<std::endl;
}

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<iostream>
#include<string>
#include<functional>

//普通函数
void Show(int n,const std::string& str){
    std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}

struct AA{
    //类中静态成员函数
    static void Show(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

struct BB{
    //类中仿函数
    void operator()(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<" ,"<<str<<std::endl;
    }
};

struct CC{
    //类的普通函数
    void Show(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

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<void(int,const std::string&)> fn1=Show;      //定义function对象包装普通函数Show
    fn1(1,"我是一只笨鸟");                                     //用function对象调用普通函数

    //类中静态成员函数
    void(*fp2)(int,const std::string&)=AA::Show;               //声明函数指针指向类中成员函数对象
    fp2(2,"我是一只笨鸟 ");                                    //用函数指针调用类中成员函数
    std::function<void(int,const std::string&)> fn2=AA::Show;  //定义function对象包装类中静态成员函数
    fn2(2,"我是一只笨鸟");                                     //用function对象调用类中静态成员函数

    //仿函数
    BB bb;
    bb(3,"我是一只笨鸟 ");                                    //用仿函数对象调用仿函数
    BB()(3,"我是一只笨鸟");                                   //仿函数匿名对象调用
    std::function<void(int,const std::string&)> fn3=BB();     //定义function对象包装仿函数
    fn3(3,"我是一只笨鸟 ");                                   //用function对象调用仿函数

    //lambad表达式
    auto lb=[](int n,const std::string& str)->void{           //调用lambad表达式
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    };
    lb(4,"我是一只笨鸟 ");
    std::function<void(int,const std::string&)> fn4=lb;
    std::function<void(int,const std::string&)> fn5=[](int n,const std::string& str)->void{
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;         //lambda可能是匿名的
    };
    fn5(4,"我是一只笨鸟 ");
    //类中非静态成员函数 注意这里包装的时候要加对象的地址
    CC cc;
    void(CC::*fp3)(int,const std::string&)=&CC::Show;         //定义类成员函数指针指向类成员函数
    (cc.*fp3)(5,"我是一只笨鸟 ");                             //用类成员函数指针调用类成员函数
    std::function<void(CC&,int,const std::string&)> fn6=&CC::Show; //定义function对象包装成员函数
    fn6(cc,5,"我是一只笨鸟 ");                               //用function对象调用成员函数

    //可以被转换为函数指针的对象
    DD dd;
    dd(6,"我是一只笨鸟 ");                                 //用来被转换为函数指针的类对象的普通函数
    std::function<void(int,const std::string&)> fn7=dd;   //定义function对象包装可以被转换为函数指针的类
    fn7(6,"我是一只笨鸟 ");
    
    std::function<void(int,const std::string&)> 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< class Fx, class… Args >
function<> bind (Fx&& fx, Args&…args);
**Fx:**需要绑定的可调用对象(可以是前两节课介绍的那六种,也可以是 function 对象) 。
**args:**绑定参数列表, 可以是左值、 右值和参数占位符 std::placeholders::_n,如果参数不是占位符,缺省为值传递, std:: ref(参数)则为引用传递。
std::bind()返回 std::function 的对象。
std::bind()的本质是仿函数。

#include<iostream>
#include<functional>
#include<string>

// void Show(int n,const std::string& str){
//     std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
// }

//普通函数
void Show(int n,const std::string& str){
    std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
}

struct AA{
    //类中静态成员函数
    static void Show(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

struct BB{
    //类中仿函数
    void operator()(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<" ,"<<str<<std::endl;
    }
};

struct CC{
    //类的普通函数
    void Show(int n,const std::string& str){
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

struct DD{
    //类中可以被转换普通函数的类
    using Func=void(*)(int,const std::string& );
    operator Func(){
        return Show; //返回普通函数的地址
    }
};

int main(){
    //std::bind返回的function模板对象,所有可以用auto fn1=Show接收之类的
    //两种输出结果是一样的
    std::function<void(int,const std::string&)> fn1=Show;
    std::function<void(int,const std::string& str)> fn2=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
    fn1(1,"我是一只笨鸟 ");
    fn2(1,"我是一只笨鸟 ");

    //当现有的函数类型Show与要求的函数类型不一样
    //意思是现有函数类型参数顺序不一样或者要求的函数类型数量少
    //这个时候就要用到std::bind绑定器(适配器),
    //用std::bind对现有函数对象进行转换,生成新的函数对象,与要求的函数对象类型匹配上
    //如果不用std::bind(适配器)的话,要实现需求的话就只能重载原来的函数达到要求,比较麻烦
    // std::function<void(const std::string&,int)> fn3=Show;
    // std::function<void(const std::string&)> fn3=Show;
    std::function<void(const std::string&,int)>fn3=std::bind(Show,std::placeholders::_2,std::placeholders::_1);
    fn3("我是一只笨鸟 ",2);
    //直接提前绑定编号3,发行对象只有一个参数对应一个std::placeholders::_1一个占位符
    std::function<void(const std::string&)> fn4=std::bind(Show,3,std::placeholders::_1);
    fn4("我是一只笨鸟 ");
    
    int n=4;
    //用std::bind()绑定的参数缺省的话是之值传递,用传引用的话用std::ref(n)
    std::function<void(const std::string&)>fn5=std::bind(Show,std::ref(n),std::placeholders::_1);
    n=100;
    fn5("我是一只笨鸟 ");

    //要发行的对象参数比原来的函数对象多,std::bind绑定的时候不用管多余的参数,调用的时候随便加什么就行
    std::function<void(int,const std::string&,int)> fn6=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
    fn6(5,"我是一只笨鸟",6);

    //普通函数
    std::function<void(int,const std::string&)> fn7=std::bind(Show,std::placeholders::_1,std::placeholders::_2);
    fn7(6,"我是一只笨鸟 ");
    
    //类的静态成员函数
    std::function<void(int,const std::string&)> fn8=std::bind(AA::Show,std::placeholders::_1,std::placeholders::_2);
    fn8(7,"我是一只笨鸟 ");

    //仿函数(函数对象)
    std::function<void(int,const std::string&)> fn9=std::bind(BB(),std::placeholders::_1,std::placeholders::_2);
    fn9(8,"我是一只笨鸟 ");

    //lambda表达式
    std::function<void(int,const std::string&)> fn10=std::bind([](int n,const std::string& str)->void{
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    },std::placeholders::_1,std::placeholders::_2);
    fn10(9,"我是一只笨鸟 ");

    //类的非静态成员函数
    CC cc;
    std::function<void(CC&,int ,const std::string&)> fn11=std::bind(&CC::Show,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3);
    fn11(cc,10,"我是一只笨鸟 ");
    //在实际开发中,对于类的非静态成员函数,发行对象调用的时候不希望把对象传过去,不适合模板
    // 为了解决这个问题,可以把对象提前绑定
    std::function<void(int,const std::string&)> fn12=std::bind(&CC::Show,&cc,std::placeholders::_1,std::placeholders::_2);
    fn12(10,"我是一只笨鸟 ");

    //可以被转换为函数指针的类对象
    DD dd;
    std::function<void(int,const std::string&)> 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<iostream>
#include<thread>
#include<functional>

void Show1(){
    //无参普通函数
    std::cout<<"亲爱的,我是一只笨鸟 "<<std::endl;
}

void Show2(const std::string& str){
    //带一个参数的普通函数
    std::cout<<"亲爱的, "<<str<<std::endl;
}

struct CC{
    void Show3(int n,const std::string& str){
        //类中带两个参数的普通函数,非静态成员函数
        std::cout<<"亲爱的 "<<n<<", "<<str<<std::endl;
    }
};

template<class Fn,class...Args>
// void Show(Fn fn,Args...args){
                                    //C++11一般要把返回类型写在后面
auto Show(Fn&& fn,Args&&...args)->decltype(std::bind(std::forward<Fn>(fn),std::forward<Args>(args)...)){
    std::cout<<"开始 "<<std::endl;
    //auto f=std::bind(fn,args...);
    auto f=std::bind(std::forward<Fn>(fn),std::forward<Args>(args)...);
    f();
    std::cout<<"结束 "<<std::endl;
    return f;
}
int main(){
    std::cout<<"================"<<std::endl;
    Show(Show1);
    auto f1=std::bind(Show1);
    f1();

    Show(Show2,"我是一只笨鸟 ");
    std::function<void(const std::string&)> f2=std::bind(Show2,std::placeholders::_1);
    f2("我是一只笨鸟 ");

    CC cc;
    Show(&CC::Show3,&cc,3,"我是一只笨鸟 ");
    std::function<void(int n,const std::string&)> f3=std::bind(&CC::Show3,&cc,std::placeholders::_1,std::placeholders::_2);
    f3(3,"我是一只笨鸟 ");
    std::cout<<"================"<<std::endl;
    //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<iostream>
#include<map>
#include<string>

/**
 * 
 * map概念总结:
 *      map的用法和python中的字典用法是类似的。
 *      map是关联式容器,按照特定的顺序存储由key和value值组成的键值对。
 *      key:常用于对元素进行排序的唯一标识,元素总是按照其key进行排序的。
 *      自动建立key-value的对应。key和value可以是任意的数据类型。
 *      map容器中没有两个元素具有相同的key。
 * 
 *      template <bool _Multi2 = _Multi, enable_if_t<!_Multi2, int> = 0>
        pair<iterator, bool> 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 <class _Iter = iterator, enable_if_t<!is_same_v<_Iter, const_iterator>, int> = 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->_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<size_type>(_STD distance(_First, _Last));
        _Erase_unchecked(_First, _Last);
        return _Num;
    }
    
*/


void Func1(){
    //map中的插入元素
    std::map<int,std::string> person;
    person[0]="tom";
    person.insert(std::pair<int,std::string>(1,"jery"));
    person.insert(std::make_pair(3,"rose"));
    person.insert(std::map<int,std::string>::value_type(4,"Speike"));
    std::pair<int,std::string> p1(5,"mary");
    person.insert(p1);

    //map中的遍历元素方法1
    std::map<int,std::string>::iterator iter1;
    for(iter1=person.begin();iter1!=person.end();iter1++){
        std::cout<<iter1->first<<" "<<iter1->second<<std::endl;
    }
    //map中的遍历元素方法2
    for(auto iter2=person.begin();iter2!=person.end();iter2++){
        std::cout<<iter2->first<<" "<<iter2->second<<std::endl;
    }
    //map中的遍历元素方法3
    for(auto iter3:person){
        std::cout<<(iter3).first<<" "<<(iter3).second<<std::endl;
    }

    //map的insert方法插入是否成功,insert插入的元素返回值是std::pari<iterator,bool>
    std::pair<std::map<int,std::string>::iterator,bool> insert_pair;
    insert_pair=person.insert(std::map<int,std::string>::value_type(6,"kuku"));
    std::cout<<"是否插入成功: "<<std::boolalpha<<(insert_pair).second<<std::endl;

    //获取map中的元素find(key),返回值是map迭代器
    std::map<int,std::string>::iterator iter4;
    iter4=person.find(6);      //6是key值
    if(iter4!=person.end()){
        std::cout<<iter4->first<<" "<<iter4->second<<std::endl;
    }else{
        std::cout<<"not find"<<std::endl;
    }

    //删除map中的元素,erase(key),或者erase(iterator)两种方法,先用find(key)返回元素的迭代器,在调用erase(iterator)删除
    std::cout<<"======================="<<std::endl;
    auto iter5=person.find(5);
    person.erase(iter5);
    person.erase(6);
    for(auto iter6:person){
        std::cout<<(iter6).first<<" "<<(iter6).second<<std::endl;
    }
    std::cout<<"======================="<<std::endl;
    //自动建立key-value的对应。key和value可以是任意的数据类型。这里改成std::string,std::string
    std::map<std::string,std::string> fruits;
    fruits["apple"]="苹果";
    fruits.insert(std::pair<std::string,std::string>("banana","香蕉"));
    fruits.insert(std::map<std::string,std::string>::value_type("pear","梨"));
    std::map<std::string,std::string>::iterator iter7=fruits.find("banana");
    if(iter7!=fruits.end()){
        std::cout<<iter7->first<<" "<<iter7->second<<std::endl;
    }else{
        std::cout<<"not find"<<std::endl;
    }
    std::cout<<"========================="<<std::endl;
    fruits.erase(iter7);
    for(auto iter8:fruits){
        std::cout<<(iter8).first<<" "<<(iter8).second<<std::endl;
    }
}

int main(){
    Func1();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44918090/article/details/128540101