首页 > 其他分享 >可调用对象包装器和绑定器

可调用对象包装器和绑定器

时间:2023-12-07 15:47:03浏览次数:34  
标签:std 调用 函数 包装 绑定 对象

文章参考:

爱编程的大丙 (subingwen.cn)

1. 可调用对象

一组执行任务的语句都可以视作一个函数、一个可调用对象。C++中提供了可调用对象的概念,其应用相当广泛,例如在处理一些回调函数、触发函数时就会用到。

可调用对象有如下几种类型:

  • 函数指针:

    int add(int a, int b){
        return a + b;
    }
    using func_ptr = int(*)(int, int);
    func_ptr p = add;
    cout << p(1, 2) << endl;
    
  • 所属类通过operator重载了()符号的对象(仿函数)

    class Test{
    public:
    	void operator()(string msg){
            cout << msg << endl;
        }
    };
    
    void main(void){
        Test t;
        t("test");			// 使用重载操作符,输出了test
        return 0;
    }
    
  • 可以被转换为函数指针的类对象:

    #include <iostream>
    #include <string>
    using namespace std;
    
    using func_ptr = void(*)(string);   // 函数指针
    
    class Test{
    public:
        static void hello(string msg){
            cout << "hello," << msg << endl;
        }
        void show(string msg){
            cout << msg << endl;
        }
        // 将对象转换为函数指针
        operator func_ptr(){
            // return show;         会报错:使用未声明但可以识别的标识符
            return hello;           // 必须使用静态函数
        }
    };
    
    int main(void){
        Test t;
        t("world");         // 可以将对象当作函数指针来调用
        return 0;
    }
    

    注意:第18行的返回值必须是静态成员函数。否则会报错use of undeclared identified "msg",即使用未声明的可识别标识符"msg"。

  • 类成员函数指针或类成员指针:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Test{
    public:
        int num;
        static int age;
    public:
        static void hello(string msg){
            cout << "hello," << msg << endl;
        }
        void show(string msg){
            cout << msg << endl;
        }
    };
    int Test::age = 10;
    
    int main(void){
        // 静态成员函数
        using func_ptr1 = void(*)(string);
        func_ptr1 p = Test::hello;
        p("zhangSan");
        // 非静态成员函数
        using func_ptr2 = void(Test::*)(string);
        func_ptr2 p2 = &Test::show;
        Test t;
        (t.*p2)("aaa");         // 因为运算符优先级,()的优先级高于*
        // 静态成员变量
        int *p3 = &Test::age; 
        *p3 = 18;
        // 非静态成员变量
        int Test::* p4 = &Test::num;
        Test t2;
        (t2.*p4) = 10;
        return 0;
    }
    

满足以上任意一个条件,即可称为可调用对象,它们又被统称为可调用类型。因为这些方法过于五花八门,较为复杂,所以C++11中提供了统一的可调用对象操作:std::functionstd::bind

2. 可调用对象包装器

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

可调用对象包装器可以直接包装以下几种可调用对象:

  • 普通函数。
  • 类的静态成员函数。
  • 仿函数。
  • 对象转换成的函数指针。(和仿函数在调用的形式上是一致

但是可调用对象包装器无法包装对象的成员变量、非静态成员函数

2.1 基本用法

头文件:

#include <functional>
std::function<返回值类型(参数类型列表)> 名字 = 可调用对象;

EG:

#include <iostream>
#include <string>
#include <functional>   // 可调用对象包装器
using namespace std;

using func_ptr = void(*)(string);       // 函数指针

class Test{
public:
    // 静态成员函数
    static void show_msg(string msg){
        cout << msg << endl;
    }
    // 仿函数
    void operator()(int id, string msg){
        cout << id << " " << msg << endl;
    }
    // 将对象包装为函数指针
    operator func_ptr(){
        return show_msg;
    }
};

// 普通对象
void show_msg(string msg){
    cout << msg << endl;
}

int main(void){
    // 包装一般函数
    function<void(string)> p1 = show_msg;
    // 包装静态成员函数
    function<void(string)> p2 = Test::show_msg;
    // 包装仿函数
    Test t1;
    function<void(int, string)> p3 = t1;
    // 包装对象转换的函数指针(可以看出:对象转换的函数指针的包装和仿函数的包装是一样的
    Test t2;
    function<void(string)> p4 = t2;
    // 调用
    p1("aaa");
    p2("bbb");
    p3(1, "ccc");
    p4("ddd");
    return 0;
}

2.2 作为回调函数使用

回调函数本身通过函数指针实现,而对象包装器可以取代函数指针的作用

EG:

#include <iostream>
#include <string>
#include <functional>   // 可调用对象包装器
using namespace std;

using func_ptr = void(*)(string);       // 函数指针

class Base{
private:
    // 将可执行对象包装类型作为成员变量
    function<void(string)> call_back;
public:
    // 用构造函数,将可执行对象传进来
    Base(function<void(string)> var): call_back(var){}
    // 执行传进来的可执行对象
    void notify(string msg){
        call_back(msg);
    }
};

class Test{
public:
    // 静态成员函数
    static void show_msg(string msg){
        cout << msg << endl;
    }
    // 仿函数(因为仿函数和对象转换的指针函数形式一致,为了避免重复,这里参数列表使用(int, string)
    void operator()(int id, string msg){
        cout << id << " " << msg << endl;
    }
    // 将对象包装为函数指针
    operator func_ptr(){
        return show_msg;
    }
};

// 普通对象
void show_msg(string msg){
    cout << msg << endl;
}

int main(void){
    // 一般函数
    Base b1(show_msg);
    b1.notify("一般函数");
    // 静态成员函数
    Base b2(Test::show_msg);
    b2.notify("静态成员函数");
    // 仿函数(因为参数列表和Base构造函数的包装器的参数列表不一致,这里忽略)
    // 包装对象转换的函数指针(可以看出:对象转换的函数指针的包装和仿函数的包装是一样的
    Test t1;
    Base b3(t1);
    b3.notify("对象转换的函数指针");
    return 0;
}

从这个例子可以看出:使用对象包装器可以非常方便的将可调用对象转换为一个函数指针,并由此实现函数的回调,程序的灵活性得到的大大的提升。

3. 绑定器

3.1 概述

作用:

std::bind用来将可调用对象和其它参数绑定在一起,得到一个仿函数,仿函数可以使用包装器std::function进行保存,并延迟调用。简单地说,有以下两种作用:

  • 将可调用对象与其参数绑定成一个仿函数。
  • 将n元(参数个数为n个)可调用对象转换为m元(参数个数为m个,m<=n)可调用对象,也就是只绑定部分参数。

语法:

  • 绑定除了类的非静态成员函数、类的成员变量以外的可调用对象:如类的静态成员函数、仿函数

    auto f = std::bind(可调用对象的地址, 绑定的参数/占位符);
    f(实参);
    
  • 绑定类的非静态成员函数/类的非静态成员变量(如果是成员变量,那么参数/占位符不用写):

    auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);
    f(实参);
    

3.2 占位符

placeholders::_n:代表占位符所在位置的参数将会被仿函数调用时第n个实参所替换。

Eg:

  • 代码:

    #include <iostream>
    using namespace std;
    
    void show_msg(int id, string name)
    {
        cout << id << "name" << endl;
    }
    
    int main(void){
        auto f1 = bind(show_msg, placeholders::_1, "zhangSan");
        f1(1);
        f1(2, "liSi");
        return 0;
    }
    
  • 输出:

    1, zhangSan
    1, zhangSan
    
  • 分析:

    第10行:绑定器绑定可调用对象show_msg。第一个参数使用占位符placeholders::_1,意思是会将调用f1时传递来的实参中第一个参数替换在占位符的位置。第二个参数是确认绑定的参数,即使在调用f1时传入了第二个参数,依旧使用绑定时确定的参数。

    因此,通过placeholders::_n占位符,我们实现仅绑定部分参数,其余参数在调用时再确定。

3.3 绑定类非静态成员函数/变量

可调用对象包装器std::function无法对类的非静态成员函数类的非静态成员变量进行包装,但是通过绑定器std::bind的配合,我们可以完美解决这个问题:

EG

  • 代码:

    #include <iostream>
    #include <string>
    #include <functional>   // 可调用对象包装器
    using namespace std;
    
    class Test{
    public:
        string _name;
    	Test(){}
        Test(string name): _name(name){}
        int add(int a, int b);
    };
    int Test::add(int a, int b){
        cout << a << "+" << b << "=" << a + b;
        return a+b;
    }
    
    int main(void){
        // 绑定非静态成员函数
        Test t1;
        auto b1 = bind(&Test::add, &t1, placeholder::_1, 0);
        b1(1);
        function<int(int, int)> f1 = b1;
        f1(1, 2);
        function<int(int)> f2 = b1;
        f2(1);
        // 绑定非静态成员变量
        Test t2("zhangSan");
        auto b2 = bind(&Test::_name, &t2);
        b2() = "liSi";
        cout << b2() << endl;
        function<string&(void)> f3 = b2();
        cout << f3() << endl;
        f3() = "wangWu";
        cout << f3() << endl;
    }
    
  • 输出:

    1+0=1
    1+0=1
    1+0=1
    liSi
    liSi
    wangWu
    
  • 分析:

    • 2127行:通过bind可以将类的非静态成员函数/非静态成员变量绑定为仿函数,从而以类的形式调用。因此第2127行可以执行。
    • 2325行:绑定器bind绑定时可以通过占位符、默认参数来实现对可调用对象参数列表中参数数量的调整,因此第2325行都是正确的。
    • 32行:对于由非静态成员 变量绑定得到的仿函数,在使用保证器进行包装时,返回值类型为该变量的类型,参数列表为空,因此使用void。需要注意:如果要对变量进行修改而不是只读,那么返回的应该是一个引用。

注意:

到这里,借助包装器和绑定器,除了类的静态成员变量,其余可调用对象 都可以进行统一的调用。`

标签:std,调用,函数,包装,绑定,对象
From: https://www.cnblogs.com/beasts777/p/17882145.html

相关文章

  • C++/CLI 包装引用 Native C++ 简单测试
    托管C++这个项目名:CppCLI。Animals.h#pragmaonceusingnamespaceSystem;namespaceZoological{publicrefclassAnimals{public:intGetLegs();voidSetLegs(intlegs);String^GetName();voidSetName(String^nam......
  • ant 想在一个target里根据参数是不是为true 去决定是否调用另一个target
    我起初是想解决同一个tomcat下运行两个相同项目的问题,这个需要web.xml里的webAppRootKey进行区分。一个为webApp.root,另一个则改为xxx.root但是需要在编译前就改为,否则运行报错<targetname="init"depends="clean"description="初始化">......<echo>初始化工作结......
  • 函数(1)基本概念,参数,调用,声明和定义
    一、C语言中函数分为库函数和自定义函数库函数:C语言本身提供的函数,有函数名、返回值类型和函数参数常用的库函数有IO函数(stdio.h),字符串操作函数(strlen),字符操作函数(大小写),内存操作函数(memset),时间/日期函数(time),数学函数(sqrt)以及其他库函数。intmain(){ chararr1[]=......
  • 函数的定义和调用
    函数的定义和调用函数的使用必须遵循’先定义,后调用’的原则。函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用,这和变量的定义是相似的。没有事先定义函数而直接调用,就相当于在引用一个不存在的’变量名’。定义函数的语法......
  • C++_调用函数以及不同数据类型
    调用其他文件中的函数add_library可以生成我们所需的动态库或者静态库,我们要把制作出的库文件发布出去供第三方使用一些函数方法单独在一个cpp的源文件中定义,然后在另一个源文件中需要用到自定义的函数时直接调用就可以了!方法1.学过c++的人可能最熟悉的方法还是利用头文件......
  • 多重继承下的虚函数调用
    C++中虚函数调用采用所谓的虚函数表(vtable)实现,对于简单的单继承,其实现如下图所示:(其中ClassA为ClassB的基类,详见深入浅出MFCP68)你也许会想到:C++支持多继承,在多继承的情况下,vatble以及内存布局该如何实现?以下也许就是你想要的答案代码:C继承于A和B,运行环境VC6.0classA......
  • 使用new关键字,是用来调用这个对象,并给了一个新名字和内存
    new关键字是用于创建对象的关键字。它会分配内存并初始化对象。当我们使用new关键字创建对象时,会自动调用该对象的构造方法。构造方法可以用于初始化类的属性,并为对象分配内存。例如,以下代码定义了一个Person类:publicclassPerson{   privateStringname;   private......
  • C++运行期多态和编译期多态(以不同的模板参数调用不同的函数)
    在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态;在泛型编程中,多态基于template(模板)的具现化与函数的重载解析,这种多态在编译期进行,因此称为编译期多态或静态多态。<h1"="">1运行期多态运行期多态的设计思想要归结到类继承体系的设计上去。对......
  • 【C语言调用Python】Py_Finalize() 时报 GC 崩溃错误。
    Py_Finalize()时报GC崩溃错误。记一次有趣的报错随笔。报错现场在使用如下的报错代码时,在释放阶段调用Py_Finalize(),报如下Assert崩溃。原因结论在调用函数逻辑里的Exit0中,对变量pModuleDict和pClass进行了手动释放,引用计数-1(宏KLP_RELEASE),这两个变量是借用的引用变量,不......
  • uniapp开发——创建安卓自定义调试基座,实现热更新调用原生功能
    一.生成本地包:选中项目,头部菜单栏“发行"-"生成本地打包App资源"打包成功二.打包完成,复制App资源包到安卓studio项目中uniapp项目根目录下,找到unpackage目录,打开resources目录,复制下边的_UNI_XXXXX格式的目录三.把App资源包粘贴到Androidstudio项目中,目录路径为:app-sr......