C++成员函数指针与std::mem_fn

Posted xkdlzy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++成员函数指针与std::mem_fn相关的知识,希望对你有一定的参考价值。

类成员函数指针(member function pointer),是C++语言的一类指针数据类型,用于存储一个指定类具有给定的形参列表与返回值类型的成员函数的访问信息。

一个指向类A成员函数的指针声明为: 

void (A::*pmf)(char *, const char *); 

  声明的解释是:pmf是一个指向A成员函数的指针,返回无类型值,函数带有二个参数,参数的类型分别是char * 和 const char *。除了在星号前增加A:: ,与声明外部函数指针的方法一样。

 基本上要注意的有两点:

  1. 成员函数指针赋值要使用 & 。( 与静态成员函数或自由函数不同,非静态成员函数不会隐式转换为成员函数指针。

  2. 使用 .*(实例对象)或者 ->*(实例对象指针)调用类成员函数指针所指向的函数

函数名可以作为函数的地址,但这是有前提条件的,从函数到指针的隐式转换是函数名在表达式中的行为,这个转换仅在表达式中才会发生,这只是函数名众多性质中的一个,而非本质,函数名的本质是函数实体的代表。

而C++规定非静态成员函数的左值不可获得,因此非静态成员函数不存在隐式左值转换, 即不存在像常规函数那样的从函数到指针的隐式转换,所以必须在非静态成员函数前使用&操作符才能获得地址。

std::mem_fn

函数模板std::mem_fn为指向成员的指针生成包装器对象,该包装器对象可以存储,复制和调用指向的成员函数。

调用时,可以使用对象的引用和指针(包括智能指针)调用std::mem_fn。

示例:

#include <iostream>
#include <functional>

struct Foo 
    void display_greeting() 
        std::cout << "Hello, world.\\n";
    
    void display_number(int i) 
        std::cout << "number: " << i << '\\n';
    
    int data = 7;
;

int main() 

    Foo* foo = new Foo;

    void (Foo::*greeting)(void) = &Foo::display_greeting;
    (foo->*greeting)();         // 类成员函数指针调用,灵活性差

    void (Foo::*display)(int) = &Foo::display_number;
    (foo->*display)(42);
   
    int Foo::*num = &Foo::data;
    std::cout << "data: " << foo->*num << '\\n';;

    Foo f;
   
    std::_Mem_fn<void(Foo::*)()> greet = std::mem_fn(&Foo::display_greeting);
    greet(f);                   // 将对象作为参数传入,可复用

    std::_Mem_fn<void(Foo::*)(int)> print_num = std::mem_fn(&Foo::display_number);
    print_num(f, 42);

    std::_Mem_fn<int Foo::* > access_data = std::mem_fn(&Foo::data);
    std::cout << "data: " << access_data(f) << '\\n';

示例二:
主线程必须启动10个工作线程,并且在启动所有这些线程之后,主函数将等待它们完成(调用join())。加入所有线程后,main函数将继续。

#include <iostream>
#include <thread>
#include <algorithm>
class WorkerThread

public:
    void operator()()    
    
        std::cout<<"Worker Thread "<<std::this_thread::get_id()<<" is Executing"<<std::endl;
    
;
int main()  

    std::vector<std::thread> threadList;
    for(int i = 0; i < 10; i++)
    
        threadList.push_back( std::thread( WorkerThread() ) );
    
    // Now wait for all the worker thread to finish i.e.
    // Call join() function on each of the std::thread object
    std::cout<<"wait for all the worker thread to finish"<<std::endl;
    std::for_each(threadList.begin(),threadList.end(),std::mem_fn(&std::thread::join));
    std::cout<<"Exiting from Main Thread"<<std::endl;
    return 0;

std::mem_fn使用

  在现代C++中,我们一般使用std::bind获取lambda表达式构造一个函数对象,然后直接调用或者作为形参供其他函数调用。那同学们是否有使用过std::mem_fn这个模板函数,我们该如何正确使用它?

一、std::mem_fn作用

  std::mem_fn官方文档介绍是这样的:std::mem_fn - cppreference.com. 大致意思是这个模板函数会生成一个执行成员指针的包裹对象,它其实也是一个函数对象,那么其类一定有一个operator()操作符了,内部实现对函数的调用。下面是visual studio 2019std::mem_fn函数实现的源码:

  1.传入一个类型所属的指针:_Ty::*_Pm,那个_Rx看栈帧信息是回调方式。

  

       2.通过_Mem_fn构造成员函数对象,保存我们传入的_Ty::*_Pm指针:

  

   3.调用operator()操作符,invoke->_Pm,并传入参数值:

  

   上面就是std::mem_fn函数的全部实现过程,如果对operator内部相关泛型编程知识不熟悉的也不影响我们对其实现原理的了解和使用。下面我们开始敲代码,看看它怎么用的。

二、std::mem_fn使用

  为了增加对该模板函数使用的印象,我将从多个方面用代码进行验证。

  全局函数:

static int Add(int a, int b)
{
    return a + b;
}

int main()
{
    std::mem_fn(Add); // ERROR : 语法无法通过
}

  其实全局函数不支持,通过文档介绍或者源码都可以意识到的,这里只是简单说明下,加深印象。

  类(结构体)函数或属性:

 1 class Test
 2 {
 3 public:
 4     void FnWithParams(int a, int b)
 5     {
 6         cout << "fnWithParams(" << a << ", " << b << ")\\n";
 7     }
 8     void FnWithoutParam()
 9     {
10         cout << "FnWithoutParam()\\n";
11     }
12     int _a = 10;
13 protected:
14     void FnProtected()
15     {
16 
17     }
18 private:
19     void FnPrivate()
20     {
21 
22     }
23     double _d;
24 };
 1 int main()
 2 {
 3     // 成员函数
 4     auto fnWithParams = mem_fn(&Test::FnWithParams);
 5     fnWithParams(Test{}, 1, 2);
 6     
 7     Test t1;
 8     auto fnWithoutParams = mem_fn(&Test::FnWithoutParam);
 9     fnWithoutParams(t1);
10     //mem_fn(&Test::FnProtected); // 保护或私有成员函数语法错误
11 
12     // 成员属性
13     Test t2;
14     t2._a = 12;
15     auto pro = mem_fn(&Test::_a);
16     auto d = pro(t2);
17     //mem_fn(&Test::_d);     // 保护或私有成员熟悉语法错误
18    
19     return 0;
20 }

        

  类(结构体)多态函数:

 1 class Base
 2 {
 3 public:
 4     void Fn()
 5     {
 6         cout << "Base::Fn()\\n";
 7     }
 8 
 9     virtual void VirtualFn()
10     {
11         cout << "Base::VirtualFn()\\n";
12     }
13 };
14 
15 class Derivd : public Base
16 {
17 public:
18     void VirtualFn() override
19     {
20         cout << "Derivd::VirtualFn()\\n";
21     }
22 };
23 
24 class Derivd1 : public Base
25 {
26 public:
27     void VirtualFn() override
28     {
29         cout << "Derivd1::VirtualFn()\\n";
30     }
31 };
 1 int main()
 2 {
 3     // 单个对象
 4     auto derivd = make_shared<Derivd>();
 5     auto virtualFn = mem_fn(&Base::VirtualFn);
 6     virtualFn(*derivd.get());
 7 
 8    // 对象向量
 9     vector<shared_ptr<Base>> vec;
10     vec.emplace_back(make_shared<Base>());
11     vec.emplace_back(make_shared<Derivd>());
12     vec.emplace_back(make_shared<Derivd1>());
13 
14     for_each(vec.begin(), vec.end(), mem_fn(&Base::VirtualFn));
15     
16     return 0;
17 }

        

  尝试换成map映射容器:

 1 int main()
 2 {
 3     map<string, shared_ptr<Base>> map;
 4     map["base"] = make_shared<Base>();
 5     map["dervid"] = make_shared<Derivd>();
 6     map["dervid1"] = make_shared<Derivd1>();
 7     for_each(map.begin(), map.end(), mem_fn(&Base::VirtualFn));
 8 
 9     return 0;
10 }

   std::mem_fn模板函数在编译的时候错误了,因为map::value_type是std::pair类型,那么这里的map元素是pair<const string, shared_ptr<Base>> 和std::mem_fn传入的参数类型不同导致的。

  内置类:

 1 int main()
 2 {
 3     
 4     vector<string> strVec;
 5     strVec.push_back("1");
 6     strVec.push_back("12");
 7     strVec.push_back("123");
 8     vector<int> lens(strVec.size());
 9 
10     transform(strVec.begin(), strVec.end(), lens.begin(), mem_fn(&string::length));  // 统计字符串长度
11 
12 
13     return 0;
14 }

三、总结

  通过上面的使用,我们发现,std::mem_fn模板函数绑定的一定是类或者结构体,且能被外部访问到;其次,它没有bind这个函数适配器好用的另外一个地方是传参,不能使用占位符,所以在STL算法中,如果需要使用std::mem_fn传入的函数不能携带参数。既然标准库提供了这个函数,它也有其他编码上的优势,比如:可以直接使用其他类公共函数,不需要自行编写。一般情况下不考虑适用,触发对代码逻辑和整洁性有好处的,可以适当使用。

以上是关于C++成员函数指针与std::mem_fn的主要内容,如果未能解决你的问题,请参考以下文章

C++成员函数指针与std::mem_fn

C++ Primer 5th笔记(chap 19 特殊工具与技术)成员函数指针

C++ 指向成员函数指针问题

C++指向对象成员函数的指针

C++指向对象成员函数的指针

C++ Primer 5th笔记(chap 19 特殊工具与技术)将成员函数用作可调用对象