C++之std::mem_fn使用和实现原理(全)

C++进阶专栏:http://t.csdnimg.cn/5mV9r

目录

1.简介

2.使用

3.实现原理

4.使用注意

5.总结


1.简介

        函数模板std :: mem_fn生成指向成员的指针的包装对象,该对象可以存储,复制和调用指向成员的指针。 调用std :: mem_fn时,可以使用对象的引用和指针(包括智能指针)。

        我理解std::mem_fn 把一个成员函数或成员变量转换成一个可调用的函数对象。函数对象的功能是借助成员函数或成员变量来实现的。这个在我们后面讲它的实现原理时你可以很清楚的理解它。

2.使用

用 std::mem_fn 来存储并执行成员函数和成员对象:

#include <functional>
#include <iostream>
#include <memory>struct Foo
{void display_greeting(){std::cout << "你好。\n";}void display_number(int i){std::cout << "数字:" << i << '\n';}int add_xy(int x, int y){return data + x + y;}template<typename... Args> int add_many(Args... args){return data + (args + ...);}auto add_them(auto... args) // 需要 C++20{return data + (args + ...);}int data = 7;
};int main()
{auto f = Foo{};auto greet = std::mem_fn(&Foo::display_greeting);greet(f);auto print_num = std::mem_fn(&Foo::display_number);print_num(f, 42);auto access_data = std::mem_fn(&Foo::data);std::cout << "data:" << access_data(f) << '\n';auto add_xy = std::mem_fn(&Foo::add_xy);std::cout << "add_xy:" << add_xy(f, 1, 2) << '\n';// 用于智能指针auto u = std::make_unique<Foo>();std::cout << "access_data(u):" << access_data(u) << '\n';std::cout << "add_xy(u, 1, 2):" << add_xy(u, 1, 2) << '\n';// 用于带形参包的成员函数模板auto add_many = std::mem_fn(&Foo::add_many<short, int, long>);std::cout << "add_many(u, ...):" << add_many(u, 1, 2, 3) << '\n';auto add_them = std::mem_fn(&Foo::add_them<short, int, float, double>);std::cout << "add_them(u, ...):" << add_them(u, 5, 7, 10.0f, 13.0) << '\n';
}

输出:

你好。
数字:42
data:7
add_xy:10
access_data(u):7
add_xy(u, 1, 2):10
add_many(u, ...):13
add_them(u, ...):42

上面代码变量greet、print_num、add_xy 、add_many、add_them用于存储Foo的类成员函数,access_data用于存储Foo的成员变量,最后使用了统一的调用格式Fn(, , ,...)来完成对函数的调用或成员变量的访问。

3.实现原理

源码面前无秘密,直接上源码(VS2019):

template <class _Memptr>
class _Mem_fn : public _Weak_types<_Memptr> {
private:_Memptr _Pm;public:constexpr explicit _Mem_fn(_Memptr _Val) noexcept : _Pm(_Val) {}template <class... _Types>_CONSTEXPR20 auto operator()(_Types&&... _Args) constnoexcept(noexcept(_STD invoke(_Pm, _STD forward<_Types>(_Args)...)))-> decltype(_STD invoke(_Pm, _STD forward<_Types>(_Args)...)) {return _STD invoke(_Pm, _STD forward<_Types>(_Args)...);}
};template <class _Rx, class _Ty>
_NODISCARD _CONSTEXPR20 _Mem_fn<_Rx _Ty::*> mem_fn(_Rx _Ty::*_Pm) noexcept {return _Mem_fn<_Rx _Ty::*>(_Pm);
}

从上面的实现可以看出:
1.std::mem_fn是模版函数,接收的参数_Pm是的struct或class的成员指针,这个指针可能是函数指针,也可能是成员变量指针;从这里就限制了std::mem_fn不能用全局函数指针。
2._Mem_fn是模版类,重载了operator(), 这就是仿函数的实现方式。类_Mem_fn的_Pm存储了类成员指针,然后最终通过std::invoke实现函数调用或成员变量访问。
3.std::invoke 是 C++17标准库中引入的一个函数模板,它提供了一种统一的调用语法,无论是调用普通函数、函数指针、类成员函数指针、仿函数、std::function、类成员还是lambda表达式,都可以使用相同的方式进行调用; 更多详细的讲解可参考我的另外一篇博客。

C++17之std::invoke: 使用和原理探究(全)_c++新特性 invoke-CSDN博客

4.使用注意

不支持的场景

不支持全局函数
不支持类protected访问权限的成员(函数或数据)
不支持类private访问权限的成员(函数或数据)

支持的场景

传入类对象
传入引用对象
传入右值
传入对象指针
传入智能指针std::shared_ptr
传入智能指针std::unique_ptr
传入派生类对象
带参数的成员函数

示例如下:

#include <functional>
#include <iostream>int Add(int a, int b)
{return a + b;
}class Age
{
public:Age(int default = 7879) : m_age(default){}bool compare(const Age& t) const{return m_age < t.m_age;}void print() const{std::cout << m_age << ' ';}int m_age;protected:void add(int n){m_age += n;}private:void sub(int m){m_age -= m;}
};class DerivedAge : public Age
{
public:DerivedAge(int default = 22) : Age(default){}
};int main(int argc, char* argv[])
{// 0.不支持的示例{// 1.不支持全局函数// auto globalFunc = std::mem_fn(Add); // ERROR: 语法无法通过// 2.不支持类protected访问权限的函数// auto addFunc = std::mem_fn(&Age::add); // ERROR: 语法无法通过// 3.不支持类private访问权限的函数// auto subFunc = std::mem_fn(&Age::sub); // ERROR: 语法无法通过}// 1.成员函数示例{auto memFunc = std::mem_fn(&Age::print);// 方式一:传入类对象Age obja{ 18 };memFunc(obja);Age& refObj = obja;refObj.m_age = 28;// 方式二:传入引用对象memFunc(refObj);// 方式三:传入右值Age objb{ 38 };memFunc(std::move(objb));// 方式四:传入对象指针Age objc{ 48 };memFunc(&objc);// 方式五:传入智能指针std::shared_ptrstd::shared_ptr<Age> pAge1 = std::make_shared<Age>(58);memFunc(pAge1);// 方式六:传入智能指针std::unique_ptrstd::unique_ptr<Age> pAge2 = std::make_unique<Age>(68);memFunc(pAge2);// 方式七:传入派生类对象DerivedAge aged{ 78 };memFunc(aged);// 方式八:带参数成员函数auto memFuncWithParams = std::mem_fn(&Age::compare);std::cout << memFuncWithParams(Age{ 25 }, Age{ 35 }) << std::endl;}std::cout << std::endl;// 2.成员变量示例{auto memData = std::mem_fn(&Age::m_age);// 方式一:传入类对象Age obja{ 19 };std::cout << memData(obja) << ' ';Age& refObj = obja;refObj.m_age = 29;// 方式二:传入引用对象std::cout << memData(refObj) << ' ';// 方式三:传入右值Age objb{ 39 };std::cout << memData(std::move(objb)) << ' ';// 方式四:传入对象指针Age objc{ 49 };std::cout << memData(&objc) << ' ';// 方式五:传入智能指针std::shared_ptrstd::shared_ptr<Age> pAge1 = std::make_shared<Age>(59);std::cout << memData(pAge1) << ' ';// 方式六:传入智能指针std::unique_ptrstd::unique_ptr<Age> pAge2 = std::make_unique<Age>(69);std::cout << memData(pAge2) << ' ';// 方式七:传入派生类对象DerivedAge aged{ 79 };std::cout << memData(aged) << ' ';}//3.写法差别// 以前写法{std::vector<Age> ages{ 1, 7, 19, 27, 39, 16, 13, 18 };std::sort(ages.begin(), ages.end(), [&](const Age& objA, const Age& objB) {return return objA.m_age < objB.m_age;;});for (auto item : ages){item.print();}std::cout << std::endl;}// 利用std::mem_fn写法{std::vector<Age> ages{ 100, 70, 290, 170, 390, 160, 300, 180 };std::sort(ages.begin(), ages.end(), std::mem_fn(&Age::compare));std::for_each(ages.begin(), ages.end(), std::mem_fn(&Age::print));std::cout << std::endl;}return 0;
}

5.总结

  • std::mem_fn 允许我们将成员函数、数据成员或指向成员的指针转换为可调用的对象。
  • 这使得我们可以更加灵活地处理成员函数,特别是在需要将其作为回调或策略参数传递时。
  • 通过 std::mem_fn,我们可以避免直接使用函数指针或成员指针,从而简化代码并提高可读性。
  • std::mem_fn 返回的对象可以存储并传递给其他函数或对象,以便稍后使用。

        需要注意的是,std::mem_fn 生成的函数对象需要接收一个对象实例作为第一个参数(对于非静态成员函数),然后才能调用该成员函数。对于数据成员,它返回的是对该成员的引用或指针,取决于数据成员的类型。        

参考:

std::mem_fn - cppreference.com

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

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

相关文章

SpringMVC基础Controller

文章目录 Controller 的编写和配置1. Controller 注解类型2. RequestMapping 注解类型3. 编写请求方法4. 请求参数和路径变量 Controller 的编写和配置 Controller 注解和 RequestMapping 注解是 Spring MVC 最重要的两个注解。 使用基于注解的控制器的优点如下&#xff1a; …

c++ 有名对象和匿名对象

c 有名对象和匿名对象 有名对象就是有名字的对象&#xff0c;匿名对象就是没有名字的对象。 #define _CRT_SECURE_NO_WARNINGS 1 using namespace std; #include<iostream> class score { public:score(){math 100;chinese 100;english 100;}score(int _math, int _…

Java Web-Tomcat

Web服务器 Web服务器是一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是“提供网上信息浏览服务”。 Tomcat&#xff0c;是一个 HTTP 服务器。我们只需要在服务器中安装一个Web服务器如Tomcat&#xff0c;然后就可以将…

面试算法-116-组合总和 II

题目 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意&#xff1a;解集不能包含重复的组合。 示例 1: 输入: candidates [10,1,…

二叉树与递归

二叉树的三种遍历方法&#xff1a; 前序遍历&#xff1a;根结点 —> 左子树 —> 右子树 中序遍历&#xff1a;左子树—> 根结点 —> 右子树 后序遍历&#xff1a;左子树 —> 右子树 —> 根结点 下面是三种遍历的代码和计算树的大小&#xff0c;计算叶子的…

C#面:选择题:关于try-catch-finally

下列关于 try…catch…finaly 语句的说明中&#xff0c;不正确的是&#xff1a; A)catch块可以有多个 B)finaly总会执行 C)catch块也是可选的 D)可以只有try块 答&#xff1a;D 解析&#xff1a; A)catch块可以有多个&#xff1a; 可以使用多个catch块来捕获不同类型的异常…

数据安全之路:Databend 用户策略指南

在 Databend 中&#xff0c;我们致力于保护用户的数据安全。除了身份认证之外&#xff0c;我们还提供了多种访问策略&#xff0c;包括网络策略&#xff08;Network Policy&#xff09;、密码策略&#xff08;Password Policy&#xff09;和数据脱敏策略&#xff08;Masking Pol…

JavaScript进阶5之垃圾回收(计算机组成、解释与编译、JavaScript引擎、垃圾回收、内存管理)、运行机制(浏览器进程分类、浏览器事件循环)

垃圾回收&运行机制 垃圾回收计算机组成解释与编译JavaScript引擎V8引擎 垃圾回收引用计数法标记清除&#xff08;mark-sweep&#xff09;算法 内存管理新生代 运行机制浏览器进程分类&#xff1a;浏览器事件循环宏任务微任务整体流程浏览器事件循环案例一案例二 垃圾回收 …

Android OpenMAX - 开篇

Android Media是一块非常庞大的内容&#xff0c;上到APP的书写&#xff0c;中到播放器的实现、封装格式的了解&#xff0c;下到OMX IL层的实现、Decoder的封装&#xff0c;每一块都需要我们下很大的功夫学习。除此之外&#xff0c;我们还要对一些相关的模块进行了解&#xff0c…

东方 - 分支(2) - 多分支

目录 解析部分&#xff1a;多分支1304. 冷饮的价格&#xff08;2&#xff09;问题描述解题思路代码实现代码解析 1044. 找出最经济型的包装箱型号问题描述解题思路代码实现代码解析 1039. 求三个数的最大数问题描述解题思路代码实现代码解析 1035. 判断成绩等级问题描述解题思路…

Unity学习日记 11.单词识别游戏

目录 1.返回鼠标单击对象的名字 2.鼠标拖动移动对象 3.实现鼠标跟随 4.场景准备工作 5.判断图片与框配对 6.根据配对结果放置图片 1.返回鼠标单击对象的名字 步骤&#xff1a; 创建一个ShowName的脚本&#xff0c;并挂载在摄像机上 RaycastHit2D hitInfo;void Update(){…

CANalyzer使用_04 使用CAN报文发送数据

本文手把手介绍使用CAN来发送数据。分为创建工程&#xff0c;创建CAN报文&#xff0c;运行效果&#xff0c;参考文献。 1 创建工程 双击“CANalyzer->单击“I accept”->等一会等软件打开后&#xff0c;单击“File”->单击"New"->双击"CAN 500kBa…

vue3+ts+element home页面侧边栏+头部组件+路由组件组合页面教程

文章目录 效果展示template代码script代码样式代码 效果展示 template代码 <template><el-container class"home"><el-aside class"flex" :style"{ width: asideDisplay ? 70px : 290px }"><div class"aside-left&q…

json文件美化工具(json tools)

自动整理json文件&#xff0c;使用&#xff1a;ctrlaltM

【数学】第十三届蓝桥杯省赛C++ A组/研究生组 Python A组/研究生组《数的拆分》(C++)

【题目描述】 给定 T 个正整数 &#xff0c;分别问每个 能否表示为 的形式&#xff0c;其中 , 为正整数&#xff0c;, 为大于等于 2 的正整数。 【输入格式】 输入第一行包含一个整数 T 表示询问次数。 接下来 T 行&#xff0c;每行包含一个正整数 。 【输出格式】 对于…

浅析JS原型链

目录 实例对象原型对象对象原型短暂总结一下constructor原型链 何为原型链呢&#xff1f; 就是实例对象和原型对象之间的链接,每一个对象都有原型,原型本身又是对象,原型又有原型,以此类推形成一个链式结构.称为原型链。 这里又扯到了另外两个概念了。 实例对象>>&g…

el-upload上传文件前端自己读取excel

1.读取方法 需要下载xlsx依赖 export const readExcelFile (file) > {return new Promise((resolve, reject) > {let reader new FileReader();reader.readAsBinaryString(file.raw);reader.onload (ev) > {try {let dataBinary ev.target.result;let workBook …

PyTorch 教程-快速上手指南

文章目录 PyTorch Quickstart1.处理数据2.创建模型3.优化模型参数4.保存模型5.加载模型 PyTorch 基础入门1.Tensors1.1初始化张量1.2张量的属性1.3张量运算1.3.1张量的索引和切片1.3.2张量的连接1.3.3算术运算1.3.4单元素张量转变为Python数值 1.4Tensor与NumPy的桥接1.4.1Tens…

腾讯云轻量4核8G12M服务器配置4C8G12M详解

4核8G是云服务器的参数&#xff0c;代表云服务器的硬件配置和网络带宽&#xff0c;4核代表CPU、8G是指内存、12M代表带宽值为12Mbps&#xff0c;腾讯云百科txybk.com以腾讯云轻量应用服务器4核8G12M带宽配置为例&#xff0c;来详细介绍下服务器参数&#xff1a; 4c8g是什么意思…

Unity学习笔记 9.2D射线

下载源码 UnityPackage 1.Ray2D 让小球向右发射射线&#xff1a; Ray2D ray;void Start() {// Ray2D(起点&#xff0c;终点)ray new Ray2D(this.transform.position, Vector2.right);// Debug.DrawLine(起点&#xff0c;终点&#xff0c;颜色&#xff0c;显示时间)Debug.DrawL…