当前位置: 首页 > news >正文

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) const
        noexcept(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_ptr
        std::shared_ptr<Age> pAge1 = std::make_shared<Age>(58);
        memFunc(pAge1);
 
        // 方式六:传入智能指针std::unique_ptr
        std::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_ptr
        std::shared_ptr<Age> pAge1 = std::make_shared<Age>(59);
        std::cout << memData(pAge1) << ' ';
 
        // 方式六:传入智能指针std::unique_ptr
        std::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

相关文章:

  • OpenAI跨界好莱坞:Sora电影制作工具即将登场,首曝剧情片细节及试用者评价
  • 【Linux】详细分析/dev/loop的基本知识 | 空间满了的解决方法
  • 【前端面试3+1】02插槽、箭头函数与普通函数、重绘重排、【回文数】
  • 计算机网络——数据链路层(差错控制)
  • 微软Microsoft Surface Go 2
  • 将word转为PDF的几种简单方式
  • Day48:WEB攻防-PHP应用文件上传中间件CVE解析第三方编辑器已知CMS漏洞
  • CMake学习笔记(一)一个最简单的CMakeLists嵌套示例
  • 使用Urllib库创建第一个爬虫程序
  • 创新研报 | 2024+人工智能安全报告
  • 基于javaweb(springboot+mybatis)生活美食分享平台管理系统设计和实现以及文档报告
  • OD_2024_C卷_100分_70、停车场车辆统计【JAVA】【逻辑分析】
  • 监控API的指标
  • MySQL内置函数
  • 机器学习 - save和load训练好的模型
  • PHP 读取嵌入式数据 SQLite3
  • 一个单生产-多消费模式下无锁方案(ygluu/卢益贵)
  • 数字乡村引领新风尚:科技赋能农村实现全面进步
  • 零基础机器学习(3)之机器学习的一般过程
  • GPT4.0
  • https://app.hackthebox.com/machines/Inject
  • Spring —— Spring简单的读取和存储对象 Ⅱ
  • 渗透测试之冰蝎实战
  • Mybatis、TKMybatis对比
  • Microsoft Office 2019(2022年10月批量许可版)图文教程
  • 《谷粒商城基础篇》分布式基础环境搭建
  • 哈希表题目:砖墙
  • Vue 3.0 选项 生命周期钩子
  • 【车载嵌入式开发】AutoSar架构入门介绍篇
  • 【计算机视觉 | 目标检测】DETR风格的目标检测框架解读