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

news2024/11/16 3:50:22

回调函数

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

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/136925.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

低代码搭建门店管理之收发货管理系统

随着电商的深入&#xff0c;不少门店都开始采用线上模式进行销售了&#xff0c;并且有的门店线上销售更是比线下销售更加火爆。因此&#xff0c;线上销售务必会涉及到收发货这个步骤。以前线上销售刚兴起的时候收发货只靠人工纸质化登记就能搞定&#xff0c;但是随着线上销售的…

盘点这些年稚晖君的DIY项目,看看他的技术栈有多强

近日&#xff0c;知名极客稚晖君在个人微博发文称自己将离职创业&#xff0c;开启一段新的旅程&#xff0c;“天才少年”将在机器人领域继续发光发热。 自2020年初发布第一个出圈视频《技术宅UP耗时三个月&#xff0c;自制B站最强小电视&#xff01;》以来&#xff0c;稚晖君共…

Vue实例的基本属性,computed计算属性,watch监听属性以及过滤器filters

目录 一、Vue实例的属性 二、Vue实例的计算属性&#xff1a;computed。计算属性结果会被缓存起来&#xff0c;当依赖的响应式属性发生变化时&#xff0c;才会重新计算&#xff0c;返回最终结果。 三、Vue实例的状态监听属性&#xff1a;watch&#xff0c;可以对元素的值的变…

JVM垃圾回收相关算法-垃圾标记阶段

文章目录学习资料垃圾回收概念概述垃圾回收相关算法垃圾标记阶段&#xff1a;对象存活判断引用计数算法可达性分析算法&#xff08;或根搜索算法、追踪性垃圾收集&#xff09;【Java使用算法】基本思路GC Roots对象的finalization机制对象处于三种可能的状态具体过程学习资料 …

WebDAV之葫芦儿·派盘+WebDAV Nav Lite

WebDAV Nav Lite 支持WebDAV方式连接葫芦儿派盘。 支持连接所有WebDAV服务器、云存储、NAS设备的管理工具,并可以直接管理设备内的文件?那快来试下WebDAV Nav Lite自动同步与管理工具吧。 WebDAV Nav Lite允许您

【Bootstrap】CSS全局样式

目录 一、HTML5文档类型 二、移动设备优先 三、禁用移动设备上的缩放功能 四、布局容器 1. container 类 ​2. container-fluid 类 五、标题 六、页面主体 七、文本 1. 内联文本元素 2. 文本对齐 ​3. 改变大小写 八、列表 1. 无序列表 2. 有序列表 3. 无样式列…

Hadoop数据仓库有哪些特征?

数据仓库(英语&#xff1a;Data Warehouse&#xff0c;简称数仓、DW),是一个用于存储、分析、报告的数据系统。数据仓库的目的是构建面向分析的集成化数据环境&#xff0c;分析结果为企业提供决策支持(Decision Support)。 数据仓库本身并不“生产”任何数据&#xff0c;其数据…

(1分钟)速通BA优化--光束法平差

SLAM中的BA优化&#xff0c;先根据相机模型和A,B图像特征匹配好的像素坐标&#xff0c;求出A图像上的像素坐标对应的归一化的空间点坐标&#xff0c;然后根据该空间点的坐标计算重投影到B图像上的像素坐标&#xff0c;重投影的像素坐标(估计值)与匹配好的B图像上的像素坐标(测量…

java流程控制的三种类型

1. 简介 在Java项目中&#xff0c;大多数的代码都是编写在一个个的类里面。每个类中还有很多个语句&#xff0c;并且会以英文的分号;来表示语句的结束。有些小白会很好奇&#xff0c;这一行行的代码语句是按照什么顺序执行的呢&#xff1f;是按照我们看到的从上到下的顺序执行…

【瑞萨RA4系列】CoreMark移植完全指南——UART输出和SysTick计时的应用

【瑞萨RA4系列开发板体验】CoreMark移植完全指南——UART输出和SysTick计时的应用 文章目录【瑞萨RA4系列开发板体验】CoreMark移植完全指南——UART输出和SysTick计时的应用一、CoreMark简介二、基础功能支持2.1 创建RASC项目2.2 确认UART引脚2.3 打开RASC配置2.4 配置UART引脚…

javac 编译期拓展之实现 CallSuper 注解功能

javac 编译期拓展之 实现 CallSuper 注解功能 背景&#xff1a; 元旦之前&#xff0c;就和朋友探讨了这么一个问题。比如我在一个父类的 a 方法里做了一些逻辑&#xff0c;这个逻辑是必须存在的&#xff0c;假如现在子类要重写这个 a 方法&#xff0c; 那么他就需要先调用父类…

docker(一):基本组成与常用命令

文章目录1. docker基本组成1.1 镜像(image)1.2 容器(container)1.3 仓库(repository)2. docker常用命令2.1 启动类命令2.2 镜像命令2.3 容器命令1. docker基本组成 1.1 镜像(image) docker镜像(image)就是一个只读的模板。镜像可以用来创建docker容器&#xff0c;一个镜像可以…

中职组网络安全2023年山东省省赛Linux 系统渗透提权

B-3:Linux 系统渗透提权 任务环境说明: 服务器场景:Server2204(关闭链接) 用户名:hacker 密码:123456 使用渗透机对服务器信息收集,并将服务器中 SSH 服务端口号作为 flag 提 交;Flag:2283/tcp 使用渗透机对服务器信息收集,并将服务器中主机名称作为 flag 提交;F…

通过keepalived实现高可用

192.168.184.128 主/heartbeat1 192.168.184.129 从/heartbeat2 192.168.184.131 漂移地址 主备基础&#xff1a;需要在128和129服务器上&#xff0c;搭建mysql主从复制 环境基础配置 128、129操作关闭防火墙 # sed -i "s/SELINUXenforcing/SELINUXdisabled/g"…

内卷加速的手机市场,如何寻找新契机?

从此前争相入局的一亿像素摄像头&#xff0c;到不断加码的快充、屏幕刷新率&#xff0c;再到眼下不那么成熟却“硬要上阵”的屏下摄像头技术&#xff0c;原本应该通过技术创新提升用户体验的手机行业&#xff0c;变得越来越内卷&#xff0c;业内人士分析认为手机内卷造成消费者…

【阶段二】Python数据分析Pandas工具使用04篇:数据预处理:数据的汇总

本篇的思维导图: 数据预处理:数据的汇总 数据透视表pivot_table()函数 透视表功能该功能的主要目的就是实现数据的汇总统计。pandas模块中的pivot_table函数就是实现透视表功能的强大函数。 代码 import numpy as

linux解压

linux中主要有.zip&#xff0c;.gz&#xff0c;.bz2及.tar.gz和.tar.bz2等压缩格式 一、.zip&#xff0c;.gz&#xff0c;.bz2格式 .zip格式语法&#xff1a; zip 压缩文件名 源文件 #压缩文件 &#xff08;也能压缩目录&#xff0c;但只会压缩第一个目录&#xff0c;目录中…

牛客网C++项目-Linux高并发服务器开发之第一章:Linux系统编程入门 学习笔记

1.1 Linux 开发环境搭建 由于仅是开发环境的搭建&#xff0c;所以只简单记述一下步骤 必备软件&#xff1a; Ubuntu 18.04 XShell-用于远程登录&#xff0c;使用SSH协议&#xff0c;TCP连接&#xff0c;端口号22 XFtp&#xff0c;本次实验中尚未用到 Visual studio code&a…

什么是蒙特卡洛学习,时序差分算法

在学习的过程中经常会看到蒙卡特洛和时序差分算法&#xff0c;到底这两个是指什么&#xff0c;今天稍微整理下&#xff0c;开始吧。蒙卡特洛1.1 蒙卡特洛方法蒙特卡罗方法又叫做统计模拟方法&#xff0c;它使用随机数(或伪随机数)来解决计算问题。比如上图&#xff0c;矩形的面…

Python全栈开发(二)——python基础语法(二)

我们昨天说了python的数据类型&#xff0c;今天说说python的缩进规则和函数、python的顺序语句结构&#xff0c;条件和分支语句以及循环语句。缩进不规范会报错&#xff08;IndentationError: unexpected indent&#xff09;&#xff0c;python的函数使用&#xff0c;从定义到实…