iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >浅谈C++11的std::mem_fn源码解析
  • 899
分享到

浅谈C++11的std::mem_fn源码解析

2024-04-02 19:04:59 899人浏览 八月长安
摘要

目录1、源码准备2、通过一个简单的例子来了解std::mem_fn的作用3、std::mem_fn源码解析3.1、std::mem_fn解析3.2、std::_Mem_fn解析3.3

1、源码准备

本文是基于GCc-4.9.0的源代码进行分析,std::mem_fn是c++11才加入标准的,所以低版本的gcc源码是没有std::mem_fn的,建议选择4.9.0或更新的版本去学习,不同版本的gcc源码差异应该不小,但是原理和设计思想的一样的,下面给出源码下载地址
Http://ftp.gnu.org/gnu/gcc

2、通过一个简单的例子来了解std::mem_fn的作用

算法是C++标准库中非常重要的组成部分,C++通过算法+容器的方式将数据结构和算法进行了分离,这样可以使用户编写代码的时候获得最大限度的灵活性。假设我们有如下类:


class Age
{
public:
    Age(int v)
        :m_age(v)
    {
    }

    bool compare(const Age& t) const
    {
        return m_age < t.m_age;
    }

    int m_age;
};

我们可以非常方便地使用vector来保存Age对象,如下:


std::vector<Age> ages{1, 7, 19, 27, 39, 16, 13, 18};

然后非常方便的利用排序算法进行排序


std::sort(ages.begin(), ages.end(), compare);

代码中的compare是额外定义的一个比较函数,通过这个函数来选择比较的对象并决定比较的结果


bool compare(const Age& t1, const Age& t2)
{
    return t1.compare(t2);
}

严格来讲,算法中要求的并不是函数,而是一个可调用对象。C++中的可调用对象包括函数、函数对象、Lambda表达式、参数绑定等等,它们都可以作为算法的传入参数,但是如果我们按如下来传入参数的话,则会在编译过程中出现错误


std::sort(ages.begin(), ages.end(), &Age::compare);


因为&Age::compare是类成员函数,并非一个可调用对象,如果我们要将它作为比较的参数传递进去的话,就得用std::mem_fn修饰它,如下所示


std::sort(ages.begin(), ages.end(), std::mem_fn(&Age::compare));

从上面的例子可以看到,std::mem_fn的作用就是将类的成员函数转换为一个可调用对象,那么问题来了,std::mem_fn是如何实现这种功能的呢?下面让我们通过分析源码,来揭开std::mem_fn的神秘面纱。

3、std::mem_fn源码解析

3.1、std::mem_fn解析

std::mem_fn位于libstdc++-v3\include\std\functional中


template<typename _Tp, typename _Class>
inline _Mem_fn<_Tp _Class::*> mem_fn(_Tp _Class::* __pm) noexcept
{
 return _Mem_fn<_Tp _Class::*>(__pm);
}

从代码中可知std::mem_fn是一个模板函数,传入参数为指向_Class类里面的某个成员函数的指针,其返回值为_Tp,而该模板函数返回的值为_Mem_fn<_Tp _Class::*>,接下来看一下_Mem_fn的实现

3.2、std::_Mem_fn解析

std::_Mem_fn位于libstdc++-v3\include\std\functional中


template<typename _Res, typename _Class, typename... _ArgTypes>
class _Mem_fn<_Res (_Class::*)(_ArgTypes...)> : public _Maybe_unary_or_binary_function<_Res, _Class*, _ArgTypes...>
{
    typedef _Res (_Class::*_Functor)(_ArgTypes...);

    template<typename _Tp, typename... _Args>
    _Res _M_call(_Tp&& __object, const volatile _Class *, _Args&&... __args) const
    {
        return (std::forward<_Tp>(__object).*__pmf)(std::forward<_Args>(__args)...);
    }

    template<typename _Tp, typename... _Args>
    _Res _M_call(_Tp&& __ptr, const volatile void *, _Args&&... __args) const
    {
        return ((*__ptr).*__pmf)(std::forward<_Args>(__args)...);
    }

    template<typename... _Args>
    using _RequireValidArgs = _Require<_AllConvertible<_Pack<_Args...>, _Pack<_ArgTypes...>>>;

    template<typename _Tp, typename... _Args>
    using _RequireValidArgs2 = _Require<_NotSame<_Class, _Tp>, _NotSame<_Class*, _Tp>, _AllConvertible<_Pack<_Args...>, _Pack<_ArgTypes...>>>;

    template<typename _Tp, typename... _Args>
    using _RequireValidArgs3 = _Require<is_base_of<_Class, _Tp>, _AllConvertible<_Pack<_Args...>, _Pack<_ArgTypes...>>>;

public:
    typedef _Res result_type;

    explicit _Mem_fn(_Functor __pmf) : __pmf(__pmf) {}

    template<typename... _Args, typename _Req = _RequireValidArgs<_Args...>>
    _Res operator()(_Class& __object, _Args&&... __args) const
    {
        return (__object.*__pmf)(std::forward<_Args>(__args)...);
    }

    template<typename... _Args, typename _Req = _RequireValidArgs<_Args...>>
    _Res operator()(_Class&& __object, _Args&&... __args) const
    {
        return (std::move(__object).*__pmf)(std::forward<_Args>(__args)...);
    }

    template<typename... _Args, typename _Req = _RequireValidArgs<_Args...>>
    _Res operator()(_Class* __object, _Args&&... __args) const
    {
        return (__object->*__pmf)(std::forward<_Args>(__args)...);
    }

    template<typename _Tp, typename... _Args, typename _Req = _RequireValidArgs2<_Tp, _Args...>>
    _Res operator()(_Tp&& __object, _Args&&... __args) const
    {
        return _M_call(std::forward<_Tp>(__object), &__object,
        std::forward<_Args>(__args)...);
    }

    template<typename _Tp, typename... _Args,
    typename _Req = _RequireValidArgs3<_Tp, _Args...>>
    _Res operator()(reference_wrapper<_Tp> __ref, _Args&&... __args) const
    {
        return operator()(__ref.get(), std::forward<_Args>(__args)...);
    }

private:
    _Functor __pmf;
};

从源代码中可以看出以下几点信息:

  • 该类继承于_Maybe_unary_or_binary_function,由于_Maybe_unary_or_binary_function和本文分析的内容没有太大关联,大家可以自行百度查询其用法,这里就不多作介绍了
  • 类中有一个成员__pmf,其类型是指向上一节传入mem_fn的那个类成员函数的指针,由构造函数初始化
  • 接下来重点看一下类中六个重载的()运算符函数,里面的操作大同小异,基本都是通过__pmf对应的类的对象(多种形式)来调用__pmf成员函数的:
    • 第一个函数_Res operator()(_Class& __object, _Args&&… __args):可以看到,其比原始的类成员函数多要求了一个传入参数,也就是__object,类型是一个类对象的引用,从函数的实现中可以看到原理就是通过这个类对象来直接调用先前那个类成员函数的(没有这个类对象就调用不成立了,因为类成员函数是无法直接调用的,这也是std::mem_fn存在的意义)
    • 第二个函数_Res operator()(_Class&& __object, _Args&&… __args):可以看到该方法第一个传入参数是一个右值引用对象,里面的实现就是通过std::move将对象进行转移而已,其它处理与前面是完全一样的
    • 第三个函数_Res operator()(_Class* __object, _Args&&… __args):可以看到该方法传入了一个对象指针,其它处理与前面是完全一样的
    • 第五个函数_Res operator()(reference_wrapper<_Tp> __ref, _Args&&… __args):可以看到该方法传入了一个被std::reference_wrapper包装的引用,流程和前面的基本一致,比较简单,这里就不多作分析了(关于std::reference_wrapper的问题大家可以看一下这篇文章《C++11的std::ref、std::cref源码解析》,里面有通过源码分析对std::reference_wrapper作出了详细的介绍,这里就不重复说明了)
    • 第四个函数_Res operator()(_Tp&& __object, _Args&&… __args):这个就比较复杂了,这个函数是为了处理传入参数是智能指针或者派生类对象的一个情况的。可以看到函数里调用了_M_call方法,第二个参数看似可有可无,其实是为了用于给_M_call区分传入参数类型是一个智能指针还是一个派生类对象的
  • _M_call实现如下,可以看到,第一个重载的形式是处理派生类对象的,第二个重载的形式是处理智能指针的,代码比较简单,这里就不多作分析了,大家可以自行看一遍就明白了

template<typename _Tp, typename... _Args>
_Res _M_call(_Tp&& __object, const volatile _Class *, _Args&&... __args) const
{
    return (std::forward<_Tp>(__object).*__pmf)(std::forward<_Args>(__args)...);
}

template<typename _Tp, typename... _Args>
_Res _M_call(_Tp&& __ptr, const volatile void *, _Args&&... __args) const
{
    return ((*__ptr).*__pmf)(std::forward<_Args>(__args)...);
}

3.3、在代码中正确使用std::_Mem_fn

示例代码如下,从上面的一大段分析可以知道,我们传入的ages[2]就是之前一直分析的那个用于调用类成员函数的那个传入对象,而ages[3]就是bool Age::compare(const Age& t)所需要的正常的传入参数了,也就是上面的可变参数里面的值。至此std::mem_fn源码也就分析完毕了


#include <functional>
#include <iOStream>
#include <alGorithm>
#include <vector>

class Age
{
public:
    Age(int v)
        :m_age(v)
    {
    }

    bool compare(const Age& t) const
    {
        return m_age < t.m_age;
    }

    int m_age;
};

bool compare(const Age& t1, const Age& t2)
{
    return t1.compare(t2);
}

int main(int argc, char* argv[])
{
    std::vector<Age> ages{1, 7, 19, 27, 39, 16, 13, 18};
    bool ret = std::mem_fn(&Age::compare)(ages[2], ages[3]);
    //std::sort(ages.begin(), ages.end(), std::mem_fn(&Age::compare));

    return 0;
}

4、总结

std::mem_fn在函数式编程中的作用是非常大的,我们可以使用std::mem_fn生成指向类成员函数的指针的包装对象,该对象可以存储,复制和调用指向类成员函数的指针。而我们实际使用的是std::mem_fn的返回值std::_Mem_fn这个类,而我们在调用std::_Mem_fn中重载的()方法时,可以使用类对象、派生类对象、对象引用(包括std::reference_wrapper)、对象的右值引用、指向对象的指针(包括智能指针)来作为第一个参数传递进去。

到此这篇关于浅谈C++11的std::mem_fn源码解析的文章就介绍到这了,更多相关C++11 std::mem_fn源码内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 浅谈C++11的std::mem_fn源码解析

本文链接: https://www.lsjlt.com/news/128486.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 浅谈C++11的std::mem_fn源码解析
    目录1、源码准备2、通过一个简单的例子来了解std::mem_fn的作用3、std::mem_fn源码解析3.1、std::mem_fn解析3.2、std::_Mem_fn解析3.3...
    99+
    2024-04-02
  • 浅谈C++11的std::function源码解析
    目录1、源码准备2、std::function简介3、源码解析3.1、std::function解析3.2、std::_Function_handler解析3.3、_Any_data...
    99+
    2024-04-02
  • 解析C++11的std::ref、std::cref源码
    目录1、源码准备2、std::ref和std::cref的作用3、std::ref相关源码解析3.1、std::ref解析3.2、std::reference_wrapper解析3....
    99+
    2024-04-02
  • 详解C++11的std::addressof源码解析
    目录1、源码准备2、std::addressof简介3、std::addressof源码解析4、总结1、源码准备 本文是基于gcc-4.9.0的源代码进行分析,std::addre...
    99+
    2024-04-02
  • C++11 中的std::function和std::bind详解
    目录1. 可调用对象2. std::function3. std::bind3.1 std::bind绑定普通函数3.2 std::bind绑定一个成员函数3.3 绑定一个引用参数4...
    99+
    2024-04-02
  • 浅谈C++11中的几种锁
    目录互斥锁(mutex)条件锁(condition_variable)自旋锁(不推荐使用)递归锁(recursive_mutex)互斥锁(mutex) 可以避免多个线程在某一时刻同时...
    99+
    2024-04-02
  • 浅谈c++11闭包的实现
    目录什么是闭包仿函数:重载 operator()std::bind绑定器std::bindstd::bind和std::function配合使用什么是闭包 一个函数,带上了一个状态,...
    99+
    2024-04-02
  • 浅谈c++11线程的互斥量
    目录为什么需要互斥量独占互斥量std::mutex原子操作为什么需要互斥量 在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着...
    99+
    2024-04-02
  • C++11 shared_ptr 与 make_shared源码剖析详解
    目录0. 前言1. 源码分析1.1 头文件1.2 构造1.2.1 shared_ptr 的移动构造函数1.2.2 shared_ptr 的拷贝构造函数1.3 赋值重载1.4...
    99+
    2024-04-02
  • 浅谈C++11中=delete的巧妙用法
    目录巧妙用法总结C++11中,当我们定义一个类的成员函数时,如果后面使用"=delete"去修饰,那么就表示这个函数被定义为deleted,也就意味着这个成员函数...
    99+
    2024-04-02
  • C++11中的智能指针shared_ptr、weak_ptr源码解析
    目录1、前言2、源码准备3、智能指针概念4、源码解析4.1、shared_ptr解析4.1.1、shared_ptr4.1.2、__shared_ptr4.1.3、__shared_...
    99+
    2024-04-02
  • 浅谈SpringMVC请求映射handler源码解读
    请求映射源码 首先看一张请求完整流转图(这里感谢博客园上这位大神的图,博客地址我忘记了): 前台发送给后台的访问请求是如何找到对应的控制器映射并执行后续的后台操作呢,其核心为Di...
    99+
    2024-04-02
  • 浅谈DNS域名解析的过程
    用户在浏览器输入www.baidu.com时,DNS域名解析大致分为以下几个过程: 浏览器客户端检查自身有没有该域名的缓存: 如果浏览器有命中,直接返回该域名对应的IP地址,解析结束; ...
    99+
    2023-09-17
    服务器 网络 前端
  • 深入浅析C# 11 对 ref 和 struct 的改进
    目录前言背景ref 字段生命周期scopedunscopedref struct 约束反射实际用例栈上定长列表栈上链表未来计划高级生命周期总结前言 C# 11 中即将到来一个可以让重...
    99+
    2024-04-02
  • 深入解析C++中的std::thread的使用
    目录std::thread简介一、C++11 线程创建二、std::thread 的构造函数中接收什么参数?三、std::thread 的搭配用法std::thread简介 C++1...
    99+
    2023-05-16
    C++ std::thread使用 C++ std::thread用法 C++ std::thread
  • Django的admin源码浅析和模仿
    admin模块: admin提供了5种接口 list_display, 指定数据展示字段,不能放多对多字段 list_display_link,哪个字段可以链接   search_fields,搜索框 search_fiekds ...
    99+
    2023-01-31
    源码 Django admin
  • 浅谈一下关于Python对XML的解析
    目录什么是XML?python对XML的解析1.SAX (simple API for XML )2.DOM(Document Object Model)3.ElementTree(...
    99+
    2023-05-12
    Python XML Python XML解析
  • 浅谈Java多线程处理中Future的妙用(附源码)
    java 中Future是一个未来对象,里面保存这线程处理结果,它像一个提货凭证,拿着它你可以随时去提取结果。在两种情况下,离开Future几乎很难办。一种情况是拆分订单,比如你的应用收到一个批量订单,此时如果要求最快的处理订单,那么需要并...
    99+
    2023-05-31
    java 多线程 ava
  • 浅谈Java中注解Annotation的定义、使用、解析
    此例子,用于说明如何在Java中对“注解 Annotation”的定义、使用和解析的操作。注解一般用于自定义开发框架中,至于为什么使用,此处不作过多说明,这里只说明如何使用,以作备记。下面例子已测试,可以正常运行通过。1、注解自定义。这里定...
    99+
    2023-05-31
    java 自定义注解 解析
  • 浅谈利用Spring的AbstractRoutingDataSource解决多数据源的问题
    在互联网的服务端开发的时候,我们很经常要在一个项目中去调用不同的数据库。在这种情况下,必然要涉及到多数据源问题。那么,我们该如何解决多数据源问题呢?有没有一种方法来动态切换数据源呢?答案是有的。万能的Spring已经给了我们解决方案——利用...
    99+
    2023-05-31
    spring abstractroutingdatasource dat
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作