在現代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傳入的函數不能攜帶參數。既然標准庫提供了這個函數,它也有其他編碼上的優勢,比如:可以直接使用其他類公共函數,不需要自行編寫。一般情況下不考慮適用,觸發對代碼邏輯和整潔性有好處的,可以適當使用。