c++ 中类成员函数做回调函数的几种方式

c++ 中类成员函数做回调函数的几种方式

引言

在C++ 编程中如果使用类成员函数作为回调函数,会遇到报错

error C2597: illegal reference to non-static member

这个错误原因为C++ 中非静态成员函数只有在创建对象时才会分配内存,对编译器来说,其无法确定回调函数的地址,所以编译器会报错。

方法

静态成员函数

按照系统报错提示将成员函数定义为静态。
缺点:
静态函数无法调用非静态成员函数,不利于函数的封装。

普通函数/友元函数

编写一个类外函数或者友元函数
缺点:
与上述方法的缺点一致

lambda 函数

用STL中的sort()做例子记录一下lambda函数用法。

int main()
{
    std::array <int, 8> nums = { 1,7,3,8,4,6,2,5 };

    std::sort(nums.begin(), nums.end(), 
            [](const int& a, const int& b) {
            return (a > b);
            });

    for (auto& n : nums){
        std::cout << n << std::endl;
    }
    return 0;
}

即用lambda函数做一个简单的回调函数。
下面介绍用lambda函数做一个封装函数,能将this传入sort中(此处会在后面分析)1

class myclass {
public:
    std::array <int, 8> nums = { 1,7,3,8,4,6,2,5 };
    
    bool max(const int& a, const int& b) {
        return a > b;
    }

    int foo() {

       auto wrapper = [&](const int& a, const int& b) {  // lambda函数在捕获列表里通过引用(&)方式
                                		  // (当然直接捕获this或者采用取值(=)也是可以的)捕获了this
                                          // 即通过wrapper函数传递this到sort函数中。
            return max(a, b);
        };

        std::sort(nums.begin(), nums.end(), wrapper);
    }
};

int main()
{
    myclass myclass1;
    myclass1.foo();

    for (auto& n : myclass1.nums) {
        std::cout << n << std::endl;
    }
}

这样也是可以运行的。
同时可以推广到其他的回调函数中,例如 stm32 CMSIS中创建线程。

class myclass {
public :
    void threadEntry()
    {
        for (;;)
        {
            ...

        }
    }
    void start_server()
    {
        auto wrapper = [](void* ctx) {  // 此处不用捕获this的原因是 
                                        // 将this通过osThreadNew传递参数的方式传入了wrapper里,
                                        // 使得编译器可以确定需要调用谁的函数。
            ((myclass *)ctx->threadEntry);
        };

        ...

        osThreadNew(wrapper, this, ...);  // 本质上是一个回调函数绑定的过程
    }
};

分析

  • 为什么需要通过一个函数封装的形式才能使类的非静态成员函数做为回调函数使用呢?

因为普通的成员函数里都会隐含一个this,在类的非静态成员函数中访问类中其他非静态成员函数时,编译器会自动传递一个this进去,从而使得在类内可以直接调用其他非静态成员函数。同时也是由于有一个this存在,使得一个callback函数作为回调函数时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数匹配失败,所以为例实现回调,类内的函数必须舍弃隐藏的this,故可以通过将成员函数转化为静态成员函数或者封装wrapper等方式来实现调用。

  • 那么为什么静态成员函数就可以呢?

因为静态成员函数属于类本身,在类加载时就已经分配内存了,可以通过className::staticFuncName直接访问,不要实例化对象就可使用。也正因为static不属于对象,而是属于类,所以静态成员函数没有this这个参数,就不会造成参数个数不匹配的情况。

其他的方式

  1. 专门写一个类的静态函数做wrapper将动态函数封装在内,例如:

    class myclass {
    public :
        void threadEntry() {
            for (;;)
            {
                // osDelay();
            }
        }
    
        static void wrapper(void* ctx) {
            ((myclass*)ctx)->threadEntry();
        }
    
        void start_server() {
            
            ...
            
            osThreadNew(wrapper, nullptr, ...); // 本质上是一个回调函数绑定的过程
        }
    };
    
  2. 重载()

  3. C++11新特性td::tr1::function和std::tr1::bind实现回调函数

  4. 虚函数特性做回调函数

  5. 将回调函数的指针,定义结构体对象的指针封装在一个结构体做回调函数

总结

程序实现方式是多种多样的,重要的是本质的理解。


  1. 见分析小节。 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C++中,成员函数可以作为回调函数使用。这是因为成员函数与普通的函数不同,它们需要通过对象来调用。回调函数通常用来在特定的事件发生时执行某个操作。 当将成员函数作为回调函数使用时,需要使用类对象来调用它。首先,我们需要定义一个回调函数的接口,在接口中声明回调函数的参数和返回类型。然后,在类中定义一个成员函数,该成员函数回调函数的接口相匹配。 接下来,在程序中创建该类的对象,并将对象的成员函数作为回调函数传递给需要注册回调函数的地方,通常是在其他类或函数中。通过传递对象的地址或引用,其他类或函数就可以调用该对象的成员函数作为回调函数。 当事件触发时,那些注册了回调函数的地方就会调用相应的成员函数。由于成员函数是通过类对象调用的,它可以访问该类对象的成员变量和成员函数,以及其他相关的类信息。 通过使用成员函数作为回调函数,可以实现更灵活的程序设计。它可以方便地将代码逻辑封装在类中,并在需要的时候进行调用。此外,成员函数作为回调函数还可以在多线程编程中起到重要的作用,可以将任务委托给不同的线程进行执行。 总的来说,C++中可以使用成员函数作为回调函数,通过类对象来调用。这种用法可以帮助我们实现更灵活和模块化的程序设计,提高代码的重用性和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值