c++ 回调函数,std::function,std::bind

news/2024/7/24 2:18:17 标签: c++, 开发语言

回调函数

回调函数的创建步骤大概为:

  1. 声明一个函数指针类型。
  2. 拟写使用回调函数的函数,将函数指针类型及变量名声明作为参数传递。
  3. 拟写符合函数指针类型的实现函数,将实现函数的指针作为参数传递给使用它的函数。

定义回调函数的指针类型,包括返回值类型、(*类型名)函数指针、参数表

typedef int (*Calc)(int a, int b);

回调函数的使用者函数

int CalcValue(int a, int b, const Calc &func) {
    return func(a, b);
}

符合函数指针类型的实现函数

int Add(int a, int b) {
    return a + b;
}

实现函数的类型必须要和函数指针的类型声明一致,也就是返回值和参数表(个数、类型)要完全一致。

typedef int (*Calc)(int a, int b);
int CalcValue(int a, int b, const Calc &func) {
    return func(a, b);
}
int Add(int a, int b) {
    return a + b;
}
int main()
{
    int a = 4;
    int b = 6;
    int c = CalcValue(a, b, Add);
    std::cout << "c: " << c << std::endl;
    return 0;
}

std::function

可调用对象:

  • 一个函数指针
  • 一个具有operator()成员函数的类对象(传说中的仿函数),lambda表达式
  • 一个可被转换为函数指针的类对象
  • 一个类成员(函数)指针
  • bind表达式或其它函数对象

std::function 是一个模板类。作用是对C++中的可调用对象进行包装,例如普通函数、成员函数、模板函数、静态函数、lambda表达式等。可以把std::function看做一个函数对象,用于表示函数这个抽象概念。std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标,若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常。

最基本的作用是,简化调用的复杂程度,统一调用的方式。如果代码中混杂着大量普通函数、模板函数、lambda,使用 std::function 是比较适合的。

std::function<returnType(argType, argType,...)> func;

模板类当中对类型的声明方式是 < 返回值类型 ( 参数类型1, 参数类型2, …) >

使用场景:

  1. 绑定一个函数(普通函数或者静态函数)
  2. 实现回调函数
  3. 作为函数入参
std::function<void(int)> f; // 这里表示function的对象f的参数是int,返回值是void
#include <functional>
#include <iostream>

struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_ + i << '\n'; }
    int num_;
};

void print_num(int i) { std::cout << i << '\n'; }

struct PrintNum {
    void operator()(int i) const { std::cout << i << '\n'; }
};

int main() {
    // 存储自由函数
    std::function<void(int)> f_display = print_num;
    f_display(-9);

    // 存储 lambda
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();

    // 存储到 std::bind 调用的结果
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();

    // 存储到成员函数的调用
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    f_add_display(foo, 1);
    f_add_display(314159, 1);

    // 存储到数据成员访问器的调用
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';

    // 存储到成员函数及对象的调用
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
    f_add_display2(2);

    // 存储到成员函数和对象指针的调用
    std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
    f_add_display3(3);

    // 存储到函数对象的调用
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);
}
#include <functional>
class A
{
    std::function<void()> callback_;
public:
    A(const std::function<void()>& f) :callback_(f) {};
    void notify(void)
    {
        callback_();
    }
};
class Foo {
public:
    void operator()(void)
    {
        std::cout << __FUNCTION__ << std::endl;
    }
};
int main(void)
{
    Foo foo;
    A aa(foo);
    aa.notify();
}
#include <functional>
void call_when_even(int x, const std::function<void(int)>& f)
{
    if (!(x & 1))
    {
        f(x);
    }
}
void output(int x)
{
    std::cout << x << " ";
}
int main(void)
{
    for (int i = 0; i < 10; ++i)
    {
        call_when_even(i, output);
    }
    std::cout << std::endl;
}

std::bind

是一个基于模板的函数,作用是绑定并返回一个 std::function 对象。
那么什么是“绑定”?它本身作为延迟计算的思想的一种实现,作为一个调用过程当中的转发者而存在,返回一个 std::function 对象。
它与 std::function 不同的是,function 是模板类,bind 是模板函数,而 bind 返回的可调用对象可以直接给 function 进行包装并保存。

为什么要进行“包装”与“转发”呢?
首先,不规范的解释是,function 的作用是包装,它可以包装类成员函数,但却无法生成类成员函数的可调用对象。而 std::bind 则是可以生成。
因此,function 与 bind 结合后,便成为了 C++ 中类成员函数作为回调函数的一种规范的实现方式。

std::bind(&funcName, std::placeholders::_1, ...);

当用作普通函数的绑定时,第一个参数是可调用对象(普通函数、lambda等),而第二个参数开始对应可调用对象的参数表。
std::placeholders::_1 代表可调用对象的第一个参数,_2就代表第二个参数,依此类推。

当用作类成员函数的绑定时,第一个参数仍然是作为类成员的可调用对象引用,第二个参数则是对象的指针,而第三个参数开始对应可调用对象的参数表。同样使用 std::placeholders::_* 依次向后推。

因为类成员函数都有一个默认的参数,this,作为第一个参数,这就导致了类成员函数不能直接赋值给std::function,需要std::bind,简言之,std::bind的作用就是转换函数签名,将缺少的参数补上,将多了的参数去掉,甚至还可以交换原来函数参数的位置。

注意

  1. 调用指向非静态成员函数指针或指向非静态数据成员指针时,首参数必须是引用或指针(可以包含智能指针,如 std::shared_ptr 与std::unique_ptr),指向将访问其成员的对象。
  2. 到 bind 的参数被复制或移动,而且决不按引用传递,除非包装于 std::ref 或 std::cref 。
  3. 允许同一 bind 表达式中的多重占位符(例如多个 _1 ),但结果仅若对应参数( u1 )是左值或不可移动右值才良好定义。
class Baseclass
{
public:
    int Add(int a, int b) { return a + b; };
};
int main()
{
    int a = 1;
    int b = 2;
    std::shared_ptr<Baseclass> ptr_class = std::make_shared<Baseclass>();
    std::function<int(int, int)> addFunc = std::bind(&Baseclass::Add, ptr_class, std::placeholders::_1, std::placeholders::_2);

    int c = addFunc(a, b);
    std::cout << "c: " << c << std::endl;
    return 0;
}

参考
c++ 回调函数与std::function使用实例


http://www.niftyadmin.cn/n/5001651.html

相关文章

Java泛型(待补充)

泛型是一种“代码模板”&#xff0c;可以用一套代码套用各种类型。 一、什么是泛型&#xff1f; 泛型就是编写模板代码来适应任意类型&#xff1b;泛型的好处是使用时不必对类型进行强制转换&#xff0c;它通过编译器对类型进行检查&#xff1b;注意泛型的继承关系&#xff1a…

电子邮件营销实例有哪些?如何做邮件营销?

可参考的电子邮件营销实例&#xff1f;营销邮件制作技巧有什么&#xff1f; 电子邮件营销是当今数字营销领域中的一个关键策略&#xff0c;旨在通过发送定制化的电子邮件与目标受众建立联系&#xff0c;提高品牌知名度、促进销售和培养客户关系。下面将介绍一些电子邮件营销的…

叉积方法,求点与线段的相对位置

叉积可以用来判断一个点在一条线段的哪个方向。 线段两个端点坐标为 A(x1, y1), B(x2, y2)&#xff0c; 假设点 P 的坐标为 (px, py)&#xff0c; 则向量 AP 和 BP 的坐标表示为&#xff1a; AP (px - x1, py - y1) BP (x2 - px, y2 - py) 叉积的计算公式为&#xff1a; (py…

Python Number(数字).............................................

Python Number 数据类型用于存储数值。 数据类型是不允许改变的,这就意味着如果改变 Number 数据类型的值&#xff0c;将重新分配内存空间。 以下实例在变量赋值时 Number 对象将被创建&#xff1a; var1 1 var2 10您也可以使用del语句删除一些 Number 对象引用。 del语句…

关于国产数据库与国外数据库之间的区别

国产数据库与国外数据库之间有以下一些区别&#xff1a; 数据存储和处理&#xff1a;一些国产数据库使用不同的数据存储和处理方式。例如&#xff0c;一些国内数据库可能更倾向于使用自研的分布式存储和处理技术&#xff0c;以满足大规模数据处理和高并发访问的要求。 安全和隐…

网络安全—0基础入门学习手册

前言 一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防…

part-01 C++知识总结(程序的内存分区、多态的实现)

总结来自&#xff1a;拓跋阿秀大佬的面试知识网站&#xff0c;侵权删 一.程序的内存分区/程序模型 内存分区分别是堆、栈&#xff0c;自由存储区&#xff0c;全局/静态存储区、常量存储区和代码存储区。 栈&#xff1a;在执行函数时&#xff0c;函数内局部变量的存储单元都可以…

计算机毕业设计 社区买菜系统 Vue+SpringBoot+MySQL

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发、系统定制、远程技术指导。CSDN学院、蓝桥云课认证讲师&#xff0c;全栈领域优质创作者。 项目内容…