包装器(C++11)

news2024/9/23 18:06:26

1. 三种可调用对象

在学习包装器之前,先回顾一下C++中三种用于定义可调用对象的方式:函数指针、仿函数(即函数对象)和 lambda 表达式。它们各有优缺点,适用于不同的场景。

a. 函数指针

  • 函数指针是指向函数的指针,可以通过它来调用函数。
  • 优点
    • 使用起来简单直接,适合指向独立的函数。
    • 支持将不同函数传递给相同的调用接口,非常灵活。
  • 缺点
    • 函数指针只能指向全局或静态函数,无法指向类的非静态成员函数。
    • 类型定义复杂,且没有类型检查的优势,容易导致意外调用错误函数。
    • 不支持状态维护,无法保存上下文信息。

b. 仿函数(函数对象)

  • 仿函数是通过重载 operator() 运算符的类或结构体,允许对象像函数一样调用。
  • 优点
    • 可以在类中保存状态(例如类的成员变量),支持复杂的逻辑。
    • 类型安全,编译时检查更严格。
    • 可以通过模板进行泛型编程,使用灵活。
  • 缺点
    • 实现上比函数指针复杂,需要定义一个类和重载 () 操作符,不适合统一类型。
    • 因为仿函数对象可以存储状态,内存消耗可能更大。

c. Lambda 表达式

  • Lambda 表达式是 C++11 引入的一种匿名函数,支持通过捕获列表将上下文变量引入到表达式中。
  • 优点
    • 简洁:可以在函数内部定义临时的可调用对象,无需显式声明函数或类。
    • 灵活:支持通过捕获列表捕获外部变量,可以是按值或按引用捕获。
    • 支持泛型:可以与 auto、模板结合使用。
    • 更好的内联优化:因为 lambda 是内联的,编译器可以对其进行更多的优化。
  • 缺点
    • 不适合过于复杂的逻辑,因为会让代码难以阅读和维护。
    • C++11 引入,老版本的编译器不支持。

为什么引入包装器?

  • 概念std::function 是一个通用的函数包装器,可以存储任何可以调用的目标,包括函数指针、仿函数和 lambda 表达式。
  • 引入的原因
    • 统一接口:函数指针、仿函数和 lambda 各有不同的语法和特点,std::function 允许我们使用统一的方式处理这些不同的可调用对象。它让代码更加灵活,并简化了接口设计。
    • 类型安全std::function 提供类型安全的调用机制,避免了函数指针的潜在问题。
    • 支持存储状态:与函数指针不同,std::function 能够存储可调用对象的状态,例如捕获的 lambda 变量或仿函数对象的状态。

总结:

  • 如果你需要简单地调用一个全局函数,函数指针足够了。
  • 如果你需要一个保存状态且可调用的对象,选择仿函数对象
  • 当需要临时的、简洁的可调用对象时,lambda 表达式是首选。
  • 如果你希望统一处理各种可调用对象,std::function 这种包装器能提供极大的便利。

2. function基础使用

C++中的function就是一种函数包装器,其本质也是一个类模板:

template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
  • Ret是可调用对象返回值的类型
  • Args是可调用对象的参数类型

下面是一个示例代码,展示如何将不同的可调用对象(函数指针、仿函数、lambda 表达式)传递给相同的调用接口,并展示 std::function 的灵活性。

#include <iostream>
#include <functional>

// 定义函数:接受整数并输出其平方
void square(int x) {
    std::cout << "Square: " << x * x << std::endl;
}

// 定义仿函数:计算整数的立方
struct Cube {
    void operator()(int x) const {
        std::cout << "Cube: " << x * x * x << std::endl;
    }
};

// 统一的调用接口,接受std::function类型的可调用对象
void call_with_10(const std::function<void(int)>& func) {
    func(10); // 将10传递给可调用对象
}

int main() {
    // 1. 使用函数指针
    std::function<void(int)> func_ptr = square;
    call_with_10(func_ptr);  // 输出 Square: 100

    // 2. 使用仿函数对象
    std::function<void(int)> functor = Cube();
    call_with_10(functor);   // 输出 Cube: 1000

    // 3. 使用lambda表达式
    std::function<void(int)> lambda = [](int x) {
        std::cout << "Lambda: " << x + 5 << std::endl;
    };
    call_with_10(lambda);    // 输出 Lambda: 15

    return 0;
}

不仅仅是可以调用以上的可调用对象,类的静态和非静态成员函数都可以,以下为具体示例:

示例代码

#include <iostream>
#include <functional>

// 定义一个简单的类
class MyClass {
public:
    static int staticVar;
    int nonStaticVar;

    static void staticFunction(int x) {
        std::cout << "Static Function: staticVar = " << staticVar << ", x = " << x << std::endl;
    }

    void nonStaticFunction(int x) {
        std::cout << "Non-static Function: nonStaticVar = " << nonStaticVar << ", x = " << x << std::endl;
    }
};

// 初始化静态成员变量
int MyClass::staticVar = 42;

int main() {
    // 1. 调用静态成员函数
    MyClass::staticFunction(10);  // 直接通过类名调用,不需要实例化对象

    // 2. 创建对象,调用非静态成员函数
    MyClass obj;
    obj.nonStaticVar = 5;
    obj.nonStaticFunction(10);  // 通过对象调用非静态成员函数

    // 3. 使用 std::function 绑定静态成员函数
    std::function<void(int)> staticFuncPtr = &MyClass::staticFunction;
    staticFuncPtr(20);  // 可以通过 std::function 调用静态成员函数

    // 4. 使用 std::function 绑定非静态成员函数
    std::function<void(MyClass&, int)> nonStaticFuncPtr = &MyClass::nonStaticFunction;
    nonStaticFuncPtr(obj, 30);  // 必须传递对象实例作为第一个参数

    return 0;
}

代码分析:

使用 std::function 绑定成员函数

  • 静态成员函数:因为静态成员函数不依赖于对象,可以直接通过 std::function 绑定并调用,类似于普通函数指针。
    std::function<void(int)> staticFuncPtr = MyClass::staticFunction;
    staticFuncPtr(20);
    
  • 非静态成员函数:非静态成员函数必须绑定到一个对象实例上,才能被调用。因此,std::function 的第一个参数必须传递对象实例。
    std::function<void(MyClass&, int)> nonStaticFuncPtr = &MyClass::nonStaticFunction;
    nonStaticFuncPtr(obj, 30);
    

总结:

  • 静态成员函数与类本身关联,不依赖于对象,可以直接通过类名调用。
  • 非静态成员函数与对象关联,必须通过对象实例调用。
  • 通过 std::function 可以统一管理和调用这些函数,但非静态成员函数需要额外传递对象实例作为参数。取静态成员函数的地址可以不用取地址运算符&,但取非静态成员函数的地址必须使用取地址运算符&,一般都可以写上。

3. 简化代码

为了更好地理解包装器的作用,下面我们通过一个具体的例子来展示如何使用包装器(例如 std::function)来简化代码,尤其是在处理不同类型的可调用对象(函数指针、仿函数、lambda 表达式)时的简化效果。

场景说明

假设我们要实现一个事件处理系统,允许用户注册不同的回调函数来处理某个事件。这些回调函数可以是普通的函数、类的成员函数(包括静态和非静态),也可以是 lambda 表达式或仿函数。为了统一管理这些不同的可调用对象,我们可以使用 std::function 作为包装器。

前后的代码对比

a. 没有包装器的代码(手动区分不同的可调用对象)
#include <iostream>

// 处理函数指针
void processFunctionPointer(void (*callback)(int)) {
    callback(42); // 执行函数指针
}

// 处理类的静态成员函数
void processStaticMember(void (*callback)(int)) {
    callback(42); // 执行静态成员函数
}

// 处理类的非静态成员函数
void processNonStaticMember(void (MyClass::*callback)(int), MyClass& obj) {
    (obj.*callback)(42); // 执行非静态成员函数
}

class MyClass {
public:
    void nonStaticFunction(int x) {
        std::cout << "Non-static function: " << x << std::endl;
    }

    static void staticFunction(int x) {
        std::cout << "Static function: " << x << std::endl;
    }
};

// 普通函数
void normalFunction(int x) {
    std::cout << "Normal function: " << x << std::endl;
}

int main() {
    MyClass obj;
    processFunctionPointer(normalFunction);
    processStaticMember(MyClass::staticFunction);
    processNonStaticMember(&MyClass::nonStaticFunction, obj);

    return 0;
}
  • 需要为每种类型的可调用对象(函数指针、静态成员函数、非静态成员函数)编写单独的处理函数,增加了代码复杂度。
b. 使用 function 的代码(简化代码)
#include <iostream>
#include <functional>  // 引入 std::function

class MyClass {
public:
    void nonStaticFunction(int x) {
        std::cout << "Non-static function: " << x << std::endl;
    }

    static void staticFunction(int x) {
        std::cout << "Static function: " << x << std::endl;
    }
};

// 普通函数
void normalFunction(int x) {
    std::cout << "Normal function: " << x << std::endl;
}

// 通用处理函数,使用std::function简化代码,统一调用接口
void processEvent(const std::function<void(int)>& callback) {
    callback(42); 
}

int main() {
    MyClass obj;

    processEvent(normalFunction);
    processEvent(MyClass::staticFunction);
    processEvent(std::bind(&MyClass::nonStaticFunction, &obj, std::placeholders::_1));
    // bind这块后面会解释
    processEvent([](int x) { std::cout << "Lambda: " << x << std::endl; });

    return 0;
}

通过使用 std::function 这样的包装器,我们可以将不同类型的可调用对象统一管理,并且简化了回调函数的处理逻辑,使代码更简洁、灵活和可扩展。这也充分体现了包装器在实际开发中的作用。

4. bind的基本使用

std::bind 是 C++ 标准库中的一个函数模板,它允许你将函数的一些参数提前绑定,生成一个新的可调用对象。std::bind 可以绑定普通函数、成员函数、以及其他可调用对象,常用于生成部分应用函数或将成员函数绑定到对象实例上。可见,bind的本质就是一个被封装过的一个类模板,会根据传入的函数和参数列表自动生成。
在这里插入图片描述

语法

auto boundFunction = std::bind(callable, arg1, arg2, ..., std::placeholders::_1, ...);
  • callable: 可调用对象,如普通函数、成员函数、仿函数或 lambda。
  • arg1, arg2,…: 需要提前绑定的参数。
  • std::placeholders::_1, std::placeholders::_2,…: 表示参数占位符,表示调用时传递的参数会被放置在对应的位置。

主要用途

  1. 绑定普通函数的一些参数,从而达到调整参数顺序或个数的效果。
  2. 绑定类的非静态成员函数到具体对象上,从而创建一个新的可调用对象。
  3. 生成可部分应用的函数

示例 1:绑定普通函数

#include <iostream>
#include <functional>

void printSum(int a, int b) {
    std::cout << "Sum: " << a + b << std::endl;
}

int main() {
    // 绑定第一个参数为 10
    auto boundFunc = std::bind(printSum, 10, std::placeholders::_1);
    boundFunc(5); // 输出 Sum: 15
    return 0;
}

分析

  • std::bind(printSum, 10, std::placeholders::_1) 创建了一个新函数 boundFunc,其中 printSum 的第一个参数被固定为 10,第二个参数由调用时提供。
  • boundFunc(5) 被调用时,相当于 printSum(10, 5)

示例 2:绑定非静态成员函数

#include <iostream>
#include <functional>

class MyClass {
public:
    void display(int x) {
        std::cout << "Value: " << x << std::endl;
    }
};

int main() {
    MyClass obj;

    // 绑定对象 obj 到成员函数 display
    auto boundFunc = std::bind(&MyClass::display, &obj, std::placeholders::_1);
    boundFunc(100); // 输出 Value: 100

    return 0;
}

分析

  • std::bind(&MyClass::display, &obj, std::placeholders::_1) 将成员函数 display 绑定到对象 obj,生成了一个可以直接调用的函数 boundFunc
  • 调用 boundFunc(100) 时,相当于执行 obj.display(100)

示例 3:绑定多个参数

#include <iostream>
#include <functional>

void multiply(int a, int b, int c) {
    std::cout << "Result: " << a * b * c << std::endl;
}

int main() {
    // 绑定 a = 2, b = 3,c 由调用时提供
    auto boundFunc = std::bind(multiply, 2, 3, std::placeholders::_1);
    boundFunc(4);  // 输出 Result: 24 (2 * 3 * 4)
    return 0;
}

std::bind(multiply, 2, 3, std::placeholders::_1) 绑定了 multiply 函数的前两个参数 23,剩余的参数 c 由调用时提供。

总结

  • std::bind 是一个强大的工具,用来将函数的某些参数固定或绑定。
  • 它常用于延迟调用、生成部分应用函数以及将非静态成员函数与对象绑定。
  • 结合 占位符,我们可以灵活地控制传递参数的位置与数量,使代码更加简洁和灵活。

包装器在实践当中使用很多,功能非常强大,但也可能让代码变得复杂,所以在使用时需要小心。如果学会了就会对代码的理解有进了一步,恭喜你~ 如果文章对你有帮助的话不妨点个赞。

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

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

相关文章

Gitlab学习(008 gitlab开发工作流GitFlow)

尚硅谷2024最新Git企业实战教程&#xff0c;全方位学习git与gitlab 总时长 5:42:00 共40P 此文章包含第27p-第p29的内容 文章目录 工作流分类集中式工作流功能开发工作流GitFlow工作流Forking工作流 各个分支的功能模拟工作环境创建分支登录领导&#xff08;项目管理者&#…

【网络安全】TCP和UDP

一、TCP/UDP对比 1.共同点&#xff1a; 都是工作在TCP/IP体系结构的传输层的协议 工作主要都是把端口号往原始数据封装 在 TCP 协议中&#xff0c;原始数据指的是应用程序产生的需要通过网络进行传输的数据。这些数据可以是各种类型的信息&#xff0c;例如文本、图像、音频、…

STM32 通过软件模拟 I2C 驱动 24Cxx 系列存储器

目录 一、AT24CXXX 系列存储器介绍1、基本信息2、寻址方式3、页地址与页内单元地址4、I2C 地址5、AT24CXX 的数据读写5.1 写操作5.1.1 按字节写5.1.2 按页写 5.2 读操作5.2.1 当前地址读取5.2.2 随机地址读取5.2.3 顺序读取 二、代码实现1、ctl_i2c2、at24c3、测试程序 I2C 相关…

c++难点核心笔记(一)

文章目录 前言C的应用领域 核心编程内存分区模型1.程序运行前2.程序运行后3.new操作符引用 函数1.概述和函数原型2.函数的定义和参数3.使用函数处理不同类型的数据4.微处理器如何处理函数调用函数的分文件编写 指针和引用什么是指针动态内存分配使用指针时常犯的编程错误指针编…

为你介绍五款超实用免费报表工具,一文说清优缺点

1. 山海鲸可视化 山海鲸可视化是一款完全免费的报表工具&#xff0c;不仅能够处理各式复杂报表&#xff0c;而且提供了非常丰富的组件和模板&#xff0c;软件操作方式为零代码的拖拽式操作&#xff0c;新手用户也能快速上手。同时&#xff0c;它附送一个免费的网站后台&#x…

JVM java主流的追踪式垃圾收集器

目录 前言 分代垃圾收集理论 标记清除算法 标记复制算法 标记整理法 前言 从对象消亡的角度出发, 垃圾回收器可以分为引用计数式垃圾收集和追踪式垃圾收集两大类, 但是java主流的一般是追踪式的垃圾收集器, 因此我们重点讲解. 分代垃圾收集理论 分代收集这种理…

腾讯云负载均衡ssl漏洞(CVE-201602183)解决

绿盟漏洞扫描腾讯云应用&#xff0c;提示有1个高危、1个中危。 看IP是应用服务器前端的负载均衡。 漏洞详细信息如下&#xff1a; 根据腾讯云文档&#xff0c;可以通过设置负载均衡加密算法设置&#xff0c;来缓解漏洞风险。 登录 负载均衡控制台&#xff0c;在左侧导航栏单击…

宸励投资专注高新技术投资,助推中小企业快速发展

宸励投资&#xff0c;作为一家新兴的互联网式新轻创型投行公司&#xff0c;专注在人工智能、专精特新及数字化美业三大板块领域&#xff0c;展现了其深厚的专业背景和卓越的引领能力。这家公司不仅在各自的领域内深耕细作&#xff0c;更通过其前瞻性的视角和独到的战略布局&…

Windows X86 远线程注入问题解惑

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

ProtoBuf介绍及安装

文章目录 序列反序列化ProtoBuf特点安装ProtoBufwindowsUbuntuCentos 序列反序列化 在网络传输过程当中&#xff0c;可以理解为&#xff1a; 发送方接收方 它们彼此要通信&#xff0c;先要定好一个规则&#xff0c;也就是协议&#xff0c;双方都能认识的结构化数据&#xff…

Linux C——网络编程

本案例运行环境&#xff1a;Ubuntu 12.04.1 LTS 1、基本概念 网络的七层模型&#xff1a; 物理层 数据链路层 网络层 传输层 会话层 表示层 应用层 其中&#xff1a;1、2、3层主要面向通过网络端到端的数据流&#xff0c; 4、5、6、7层定义了程序的功能 …

静态链接和动态链接的Golang二进制文件

关注TechLead&#xff0c;复旦博士&#xff0c;分享云服务领域全维度开发技术。拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;复旦机器人智能实验室成员&#xff0c;国家级大学生赛事评审专家&#xff0c;发表多篇SCI核心期刊学术论文&#xff0c;阿里云认…

李沐 模型选择、过拟合和欠拟合相关代码【动手学深度学习v2】

多项式回归 生成数据集 给定x,我们将使用以下三阶多项式来生成训练和测试数据的标签: y=5+1.2x−3.4+5.6+ϵ where ϵ∼( ). 噪声项ϵ服从均值为0且标准差为0.1的正态分布。 在优化

GraphRAG与VectorRAG我都选:HybridRAG

从金融应用中产生的非结构化文本数据&#xff08;如财报电话会议记录&#xff09;提取和解释复杂信息&#xff0c;即使采用当前最佳实践使用检索增强生成&#xff08;RAG&#xff09;技术&#xff0c;对于大型语言模型&#xff08;LLMs&#xff09;来说仍存在重大挑战。这些挑战…

【游戏党必看】2024年最适合玩游戏的电脑系统推荐!

许多玩家都在问如果在电脑上玩游戏装什么系统好呢&#xff1f;以下系统之家小编给大家推荐两款专门为游戏玩家打造的操作系统&#xff0c;针对大型游戏进行了深度优化&#xff0c;显著提升了系统性能&#xff0c;确保游戏运行更为流畅无阻&#xff0c;能完美兼容各种类型的游戏…

三好夫人|最强“逼”婚神器,送完一次就领证

三好夫人&#xff5c;揭秘最强“逼”婚神器&#xff0c;让你的爱情甜蜜升级&#xff0c;速领见家长通行证&#xff01; 男人们请记住&#xff0c;如果一个女生给你送三好夫人&#xff0c;那么你赶快带她见家长把婚事定了。 在这个快节奏的时代&#xff0c;爱情似乎也被按下了快…

基于51单片机的电机控制和角度检测

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用滑动变阻器连接ADC0832数模转换器模拟角度传感器&#xff0c;然后通过LCD1602显示数值&#xff0c;然后按键按下不动&#xff0c;电机正转&#xff0c;松开停止。第二…

显示和隐藏图片【JavaScript】

使用 JavaScript 来实现显示和隐藏图片。下面是一个简单的示例&#xff0c;展示如何通过按钮点击来切换图片的可见性。 实现效果: 代码&#xff1a; <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name&…

神奇的交互!Ethernet IP转Profinet网关与发那科机器人的数据交互

在当今的工业领域&#xff0c;随着自动化程度的不断提高&#xff0c;工业化升级已成为必然趋势。在这个过程中&#xff0c;对机器人的联网需求变得日益迫切。机器人作为工业生产中的重要组成部分&#xff0c;其高效运行和与其他设备的协同工作对于提高生产效率至关重要。然而&a…

EI-Bisynch协议

EI-Bisynch&#xff08;Extended Interface-Bisynchronous&#xff09;协议是一种早期用于设备通信的协议&#xff0c;主要用于工业控制系统中的串行通信。随着技术的发展&#xff0c;EI-Bisynch的使用已经大幅减少&#xff0c;逐渐被更现代化、灵活性更高的通信协议&#xff0…