C++ 11新特性之function

概述

        C++ 11标准库引入了许多创新功能,其中之一便是std::function。作为函数对象容器,std::function允许开发者在编译时不知道具体类型的情况下,存储和传递任意可调用对象,极大地提升了代码的灵活性和可复用性。本文将详细探讨std::function的工作原理、使用场景及其在现代C++编程中的重要地位。

        std::function是C++ 11中<functional>头文件中定义的一个类模板,它能够存储并调用任何具有匹配签名的可调用对象,包括:普通函数、成员函数、Lambda表达式、仿函数等,而且可以给函数添加状态。

function的构成

        function是一组函数对象包装类的模板,实现了一个泛型的回调机制。function与函数指针比较相似,优点在于它允许用户在目标的实现上拥有更大的弹性。

        声明一个function时,需要给出所包装的函数对象的返回值类型和各个参数的类型。其基本声明形式如下:

          std::function<返回类型(参数列表)>

        比如:声明一个function,它返回一个bool类型并接受一个int类型和一个float类型的参数,可以像下面这样:

          function<bool(int, float)> f;

function的接口

        1、function();

        缺省构造函数,创建一个空的函数对象。如果一个空的function被调用,将会抛出一个类型为bad_function_call的异常。

        2、template <typename F> function(F g);

        这个泛型的构造函数接受一个兼容的函数对象,即这样一个函数或函数对象,它的返回类型与被构造的function的返回类型或者一样,或者可以隐式转换,并且它的参数也要与被构造的function的参数类型或者一样,或者可以隐式转换。注意,也可以使用另外一个function实例来进行构造。如果这样做,并且function g为空,则被构造的function也为空。使用空的函数指针和空的成员函数指针也会产生空的function。

        3、template <typename F> function(reference_wrapper<F> g);

        这个构造函数与前一个类似,但它接受的函数对象包装在一个reference_wrapper中,用以避免通过值来传递而产生函数或函数对象的一份拷贝。这同样要求函数对象兼容于function的签名。

        4、function& operator=(const function& g);

        赋值操作符保存g中的函数或函数对象的一份拷贝;如果g为空,被赋值的函数也将为空。

        5、template<typename F> function& operator=(F g);

        这个泛型赋值操作符接受一个兼容的函数指针或函数对象。注意,也可以用另一个 function 实例(带有不同但兼容的签名)来赋值。这同样意味着,如果g是另一个function实例且为空,则赋值后的函数也为空。赋值一个空的函数指针或空的成员函数指针也会使function为空。

        6、bool empty() const;

        这个成员函数返回一个布尔值,表示该function是否含有一个函数或函数对象。如果有一个目标函数或函数对象可被调用,它返回false 。因为一个function可以在一个布尔上下文中测试,或者与0进行比较,因此这个成员函数可能会在未来版本的库中被取消,你应该避免使用它。

        7、void clear();

        这个成员函数清除 function, 即它不再关联到一个函数或函数对象。如果function已经是空的,这个调用没有影响。在调用后,function肯定为空。令一个function为空的首选方法是赋0给它;clear可能在未来版本的库中被取消。

        8、result_type operator()(Arg1 a1, Arg2 a2, ..., ArgN aN) const;

        调用操作符是调用function的方法。你不能调用一个空的 function ,那样会抛出一个bad_function_call的异常。调用操作符的执行会调用function中的函数或函数对象,并返回它的结果。

绑定与赋值

        std::function可以接受各种类型的可调用对象,并通过调用其operator=进行赋值。比如:我们可以将一个Lambda表达式或全局函数绑定到std::function实例。

addFunction = [](int a, int b) { return a + b; };
// 或者是一个全局函数
int add(int a, int b) { return a + b; }
addFunction = &add;

        std::function的使用场景主要包括如下几处。

        事件处理:在设计事件驱动程序时,可以使用std::function来表示事件处理器,方便用户自定义处理逻辑。

        策略模式:在实现策略模式时,不同的算法策略可以通过std::function封装,使得在运行时动态切换策略变得简单。

        回调函数:许多异步操作需要提供一个完成后的回调函数,此时std::function可以作为统一的回调接口。

        泛型编程:编写通用组件时,如果需要接受一个可调用对象作为参数,std::function可以帮助我们写出更加灵活的API。

function的使用

        下面分别给出使用function来包装普通函数,函数对象和类的成员函数的示例代码。

        1、普通函数

int Add(int x, int y)
{return x+y;
}function<int (int,int)> f = Add;
int z = f(2, 3);

        2、函数对象

#include <iostream>
#include <functional>
using namespace std;class CStudent
{
public:void operator() (string strName, int nAge){cout << strName << " : " << nAge << endl; }
};int main()
{CStudent stu;function<void (string, int)> f = stu;f("Mike",  12);return 0;
}

        3、类的成员函数

struct TAdd
{int Add(int x,int y){return x + y;}
};function<int (TAdd *, int, int)> f = TAdd::Add;
TAdd tAdd;
// 如果前面的模板参数为传值或引用,直接传入tAdd即可
f(&tAdd, 2, 3);

        接下来,我们看看使用function来保存函数对象状态的情况。考虑下面的示例代码:

class CAdd
{
public:CAdd():m_nSum(0) { NULL; }int operator()(int i){m_nSum += i;return m_nSum;}int Sum() const {return m_nSum;}private:int m_nSum;
};int main() 
{CAdd add;function<int (int)> f1 = add;function<int (int)> f2 = add;cout << f1(10) << "," << f2(10) << "," << add.Sum() << endl;return 0;
}

        可能与大家想象的结果不一样,上面程序的输出是:10,10,0。我们将同一个函数对象赋值给了两个function,然后分别调用了这两个function,但函数对象中m_nSum的状态并没有被保持,问题出在哪儿呢?这是因为function的缺省行为是拷贝一份传递给它的函数对象,于是f1和f2中保存的都是add对象的拷贝,调用f1和f2后,add对象中的值并没有被修改。

        C++ 11中提供了ref和cref函数,来提供对象的引用和常引用的包装。要使function能够正确地保存函数对象的状态,我们可以这样来修改上面的示例代码。

CAdd add;
function<int(int)> f1 = ref(add);
function<int(int)> f2 = ref(add);

        另外,在两个function之间赋值时,如果源function保存的是函数对象的拷贝,则目标function保存的也是函数对象的拷贝。如果源function保存的是函数对象的引用,则目标function保存的也是函数对象的引用。

总结

        std::function虽然提供了极大的便利性和灵活性,但需要注意的是,它内部会封装一个类型擦除机制,这可能会带来额外的内存开销和间接调用的成本。因此,在对性能要求极高的场合,直接使用原始的可调用对象或者定制特定的函数指针类型可能更为高效。

        std::function作为C++ 11中的一个重要工具,为程序员提供了强大的可调用对象封装能力,简化了代码设计,提高了代码的可读性和可维护性。然而,任何技术选择都需要权衡利弊,在追求灵活性的同时,适时地评估性能影响,以确保在实际项目中做出最优决策。

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

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

相关文章

本周五上海见 第二届证券基金行业先进计算技术大会暨2024低时延技术创新实践论坛(上海站)即将召开

低时延技术是证券基金期货领域业务系统的核心技术&#xff0c;是打造极速交易系统领先优势的关键&#xff0c;也是证券基金行业关注的前沿技术热点。 1月19日下午&#xff0c;第二届证券基金行业先进计算技术大会暨2024低时延技术创新实践论坛&#xff08;上海站&#xff09;即…

springmvc上传与下载

文件上传 结构图 导入依赖 <dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>org.springframework</groupId><artifactId…

HTML中常用标签--详解

目录 1.b/strong标签 2.i/em 标签 3.u标签 4.del删除线 5.br换行 6.p标签 * 7.pre 预处理标签 8.span标签** 9.div标签*** 10.sub标签 11.sup标签 12.hr标签 13.hn标签 14.HTML5中语义标签 特殊字符 15.多媒体标签 img*** a 标签*** 第一种用法&#xff1a;…

MySQL之视图索引

学生表&#xff1a;Student (Sno, Sname, Ssex , Sage, Sdept) 学号&#xff0c;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;所在系 Sno为主键 课程表&#xff1a;Course (Cno, Cname,) 课程号&#xff0c;课程名 Cno为主键 学生选课表&#xff1a;SC (Sno, Cno, Score)…

VSCODE使用CMAKE显示命令无法找到

背景&#xff1a;使用了code server&#xff0c;安装CMAKE和CMAKE TOOLS&#xff0c;但是通过ctrlshiftp打开命令面板&#xff0c;运行随便一个cmake指令&#xff0c;都出现了指令无法找到。具体为“命令"CMake: 配置"导致错误 (command ‘cmake.configure’ not fou…

vue3 系列:Vue3 官方文档速通

前言&#xff1a;参考Vue 官方文档&#xff0c;本文档主要学习组合式 API。 一 开始 1. 简介 1.1 什么是 Vue&#xff1f; 一款 JS 框架&#xff0c;并有两个核心功能&#xff1a;声明式渲染、响应性。 1.2 渐进式框架 根据不同的需求场景&#xff0c;使用不同方式的 Vue&am…

GPT APP的开发步骤

开发一个GPT&#xff08;Generative Pre-trained Transformer&#xff09; Store&#xff08;存储&#xff09;涉及到使用预训练的语言模型&#xff08;例如GPT-3&#xff09;来生成和管理内容。以下是一般的步骤&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&…

探索设计模式的魅力:抽象工厂模式的艺术

抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;用于在不指定具体类的情况下创建一系列相关或相互依赖的对象。它提供了一个接口&#xff0c;用于创建一系列“家族”或相关依赖对象&#xff0c;而无需指定它们的具体类。 主要参…

[足式机器人]Part2 Dr. CAN学习笔记- Kalman Filter卡尔曼滤波器Ch05-1+2

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - Kalman Filter卡尔曼滤波器 Ch05-12 1. Recursive Algirithm 递归算法2. Data Fusion 数据融合Covarince Matrix协方差矩阵State Space状态空间方程 Observation观测器 1. Recursive Algirithm…

docker环境下mongo副本集的部署及异常修复

最近更换了办公地点。部署在本地docker环境里的mongo数据库不能使用了。原因是本地的ip地址变更。以前的mongo副本集的配置需要更新。处理完后&#xff0c;索性重新记录一下mongo副本集在docker中的部署流程。 mongo的事务及副本集 我们先了解一下什么是事务&#xff0c;事务…

nestjs之定义provider的几种方式

在NestJS中可以以多种方式自定义Providers。以下是一些常见的方法&#xff1a; 1. 值提供者&#xff08;Value Providers&#xff09; 值提供者用于提供一些硬编码的值&#xff0c;例如配置对象或常量。这类提供者通过 useValue 关键字来指定要注入的值。 使用过程&#xff…

RK356x基于Ubuntu20.04搭建ROS开发环境

RK356x基于Ubuntu20.04搭建ROS开发环境 1、开发环境2、Ubuntu下安装步骤3、验证成果1、开发环境 CPU:RK356x 操作系统:arm64 Ubuntu20.04 2、Ubuntu下安装步骤 1、首先确保开发板是可以联网的。 root@hyb-RK356x:~# ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,M…

selenium-java中切换iframe

1、当iframe中有固定的name或者id时可以通过name和id进行切换,代码如下 driver.switchTo().frame("name"); 2、当iframe中没有固定的name或者id时可以通过iframe角标进行切换&#xff0c;在浏览器通过ctrlf快捷键&#xff0c;搜索标签框输入//iframe;来查看当前ifr…

VsCode 常见的配置

转载&#xff1a;Visual Studio Code 常见的配置、常用好用插件以及【vsCode 开发相应项目推荐安装的插件】 - 知乎 (zhihu.com) 一、VsCode 常见的配置 1、取消更新 把插件的更新也一起取消了 2、设置编码为utf-8&#xff1a;默认就是了&#xff0c;不用设置了 3、设置常用的…

Chrome 开发者工具

Chrome 开发者工具 介绍控制面板时间线下载信息概要请求列表单个请求时间线优化时间线上耗时项 lighthouse 插件Performance&#xff08;性能指标&#xff09;Accessibility&#xff08;可访问性&#xff09;Best Practices&#xff08;最佳实践&#xff09;SEO&#xff08;搜索…

Luckysheet类似excel的在线表格(vue)

参考文档&#xff1a;快速上手 | Luckysheet文档 一、引入 在vue项目的public文件夹下的index.html的<head>标签里面引入 <link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/luckysheetlatest/dist/plugins/css/pluginsCss.css /><link relstylesheet hre…

2024,会更好嘛?

2023转眼过去&#xff0c;从1月的前端&#xff0c;2月的java&#xff0c;3月的数据库&#xff0c;4月的运维&#xff0c;我尝试了许多技术方向&#xff0c;终究在2023年5月&#xff0c;凭着背的Java面试题&#xff0c;拿到了三四个offer。2023年6月&#xff0c;边玩边整自己的毕…

okcc呼叫中心怎样提高客户服务水平

作为一个企业&#xff0c;以客户为中心一直是最重要的经营理念之一。提供高效、便捷和优质的客户服务&#xff0c;已成为创造满意客户和推动业务增长的关键因素&#xff0c;而呼叫中心系统作为一种全新的客户服务解决方案&#xff0c;正在成为越来越多企业的选择。 什么是呼叫…

springboot(ssm旅游信息管理系统 在线旅游系统 Java系统

springboot(ssm旅游信息管理系统 在线旅游系统 Java系统 开发语言&#xff1a;Java 框架&#xff1a;ssm/springboot vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xff08;或8.0&#xff09; 数…

Dockerfile镜像实战

目录 一 构建SSH镜像 1.开启ip转发功能 2. 准备工作目录 3.修改配置文件 5.启动容器并修改root密码 二 构建Systemctl镜像 1. 准备工作目录 ​编辑2.修改配置文件 3.生成镜像 4.启动容器&#xff0c;并挂载宿主机目录挂载到容器中&#xff0c;进行初始化 5.进入容器 三…