主页 > 互联网  > 

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

标签:

C++之std::mem_fn使用和实现原理(全)由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“C++之std::mem_fn使用和实现原理(全)