在现代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传入的函数不能携带参数。既然标准库提供了这个函数,它也有其他编码上的优势,比如:可以直接使用其他类公共函数,不需要自行编写。一般情况下不考虑适用,触发对代码逻辑和整洁性有好处的,可以适当使用。