C++(STL原始碼):37---仿函式(函式物件)原始碼剖析
一、仿函式概述
- 仿函式(functors)是早期的命名,C++標準規則定案後所採用的的新名稱是函式物件(function objects)
- 仿函式的作用是什麼?從前面的演算法解析可以看出,有的演算法提供第二個版本,該版本提供允許使用者指定任何“操作”,然後以該操作來決定演算法的執行功能。將這種“操作”當做演算法的引數,先將該操作設計為一個函式,再將函式指標當做演算法的一個引數或者將該“操作”設計為一個所謂的仿函式(就語言層面來說是個class),再以該仿函式產生一個物件,並以此物件作為演算法的一個引數
- 上面提到,既然函式指標可以達到“將函式當做演算法的引數”,那又為什麼設計仿函式呢?是因為函式指標不能滿足STL對抽象性的要求,也不能滿足軟體積木的要求——函式指標無法和STL其他元件(如配接器adapter)搭配,產生更靈活的變化
仿函式就是一個“行為類似函式”的物件
- 為了能夠達到“行為類似函式”的目的,其型別定義中必須自定義(或者說過載、改寫)function call運運算元(operator())
- 擁有這樣的運運算元後,我們就可以在仿函式的物件後面加上一對小括號,以此來呼叫仿函式所定義的operator()
- 例如下面就是呼叫STL提供的greater仿函式:
- 第一種用法是產生一個名為id的物件,然後呼叫其operator()
- 第二種呼叫方式是產生一個臨時(無名的)物件,然後呼叫其operator()。這種方式才是仿函式的主流用法
#include <iostream> #include <functional> using namespace std; int main() { greater<int> ig; std::cout << boolalpha << ig(4, 6) << std::endl; std::cout << greater<int>()(6, 4) << std::endl; return 0; } //boolalpha是把bool值顯示為true或false
仿函式的分類
- 以操作符劃分:分為一元仿函式與二元仿函式(沒有其他的了)
- 以功能劃分:分為算術運算、關係運算、邏輯運算
- 應用層標頭檔案為:<functional>;STL原始碼實現於<stl_function.h>
二、可配接的關鍵
- 在STL六大元件中,仿函式是體積最小、實現最容易的一種。而仿函式扮演著一種“策略”角色:
- ①可以讓STL演算法有更靈活的演出
- ②更加靈活的關鍵在於STL仿函式的可配接性
- 仿函式可以讓函式配接器(function adapter,見後面介紹)修飾,彼此串接在一起。為了擁有配接能力,每一個仿函式必須定義自己的相應型別:
- 這些型別是為了讓配接器能夠取出,獲得仿函式的某些資訊。相應的型別都只是一些typedef,所有必要操作在編譯器就能全部確定
- 仿函式的相應型別主要用來表現仿函式引數型別和返回值型別
- 這就類似於迭代器如果想要融入STL,迭代器定義了自己的5個相應型別
- 為了方便起見,<stl_function.h>中定義了兩個類,分別代表一元仿函式和二元仿函式,其中沒有任何data members或member functions,唯有一些型別定義
- 對於仿函式,只要根據個人需要選擇繼承其中一個就可以了,便自動擁有了那些相應型別,也就自動有用了配接能力
unary_function
- unary_function用來表現一元函式的引數型別和返回值型別
- 其定義如下:
//STL規定,每一個Adaptable Unary Function都應該繼承這個類 template <class Arg, class Result> struct unary_function { typedef Arg argument_type; //引數型別 typedef Result result_type; //返回值型別 };
- 一旦某個仿函式繼承了unary_function,其使用者就可以取得該仿函式的引數型別,或其返回值型別(下面未顯示):
//此仿函式繼承於unary_function template<class T> struct negate :public unary_function<T, T>{ T operator()(const T& x)const { return -x; } }; //以下配接器用來表示某個仿函式的邏輯負值 template<class Predicate> class unary_negate { //... public: //通過typename獲得其中的引數型別 bool operator()(const typename Predicate::argument_type& x)const { //... } //... };
- 在後面介紹仿函式配接器的原始碼時可以見到
binary_function
- binary_function用來表現二元函式的引數型別和返回值型別
- 其定義如下::
//STL規定,每一個Adaptable Binary Function都應該繼承這個類 template <class Arg1, class Arg2, class Result> struct binary_function { typedef Arg1 first_argument_type; //第1個引數的型別 typedef Arg2 second_argument_type; //第2個引數的型別 typedef Result result_type; //返回值型別 };
- 一旦某個仿函式繼承了binary_function,其使用者就可以取得該仿函式的引數型別,或其返回值型別(下面未顯示):
//此仿函式繼承於binary_function template<class T> struct plus :public binary_function<T, T, T>{ T operator()(const T& x, const T& y)const { return x + y; } }; //以下配接器用來將某個二元仿函式轉換為一元仿函式 template<class Operation> class binder1st { //... protected: Operation op; typename Operation::fist_argument_type value; //將其引數1型別,別名為value public: //下面是operator()的定義 typename Operation::result_type operator()(const typename Operation::second_argument_type& x) { //... } //... };
- 在後面介紹仿函式配接器的原始碼時可以見到
四、算術類(Arithmetic)仿函式
- STL內建的“算術類仿函式”,支援加法、減法、乘法、除法、模數(餘數)、和否定運算
- 除了“否定”運算為一元運算,其餘都是二元運算
- 包含如下:
- 加法:plus<T>
- 減法:minus<T>
- 乘法:multiplies<T>
- 除法:divides<T>
- 模取(modulus):modulus<T>
- 否定(negation):negate<T>
- 例如下面的程式碼表示要以1位基本元素,對vector中的每一個元素進行乘法運算:
accumulate(iv.begin(), iv.end(), multiplies<int>());
原始碼如下:
template <class T> struct plus :public binary_function<T, T, T> { T operator()(const T& x, const T& y)const { return x + y; } }; template <class T> struct minus :public binary_function<T, T, T> { T operator()(const T& x, const T& y)const { return x - y; } }; template <class T> struct multiplies :public binary_function<T, T, T> { T operator()(const T& x, const T& y)const { return x * y; } }; template <class T> struct divides :public binary_function<T, T, T> { T operator()(const T& x, const T& y)const { return x / y; } }; template <class T> struct modulus :public binary_function<T, T, T> { T operator()(const T& x, const T& y)const { return x % y; } }; template <class T> struct negate :public unary_function<T, T> { T operator()(const T& x)const { return -x; } };
演示案例
#include <iostream> #include <functional> using namespace std; int main() { plus<int> plusobj; minus<int> minusobj; multiplies<int> multipliesobj; divides<int> dividesobj; modulus<int> modulusobj; negate<int> negateobj; std::cout << plusobj(3, 5) << std::endl; std::cout << minusobj(3, 5) << std::endl; std::cout << multipliesobj(3, 5) << std::endl; std::cout << dividesobj(3, 5) << std::endl; std::cout << modulusobj(3, 5) << std::endl; std::cout << negateobj(3) << std::endl; std::cout << "**************************" << std::endl; std::cout << plus<int>()(3, 5) << std::endl; std::cout << minus<int>()(3, 5) << std::endl; std::cout << multiplies<int>()(3, 5) << std::endl; std::cout << divides<int>()(3, 5) << std::endl; std::cout << modulus<int>()(3, 5) << std::endl; std::cout << negate<int>()(3) << std::endl; return 0; }
證同元素(identity element)
- 所謂“運算op的證同元素”,意思是說數值A若與該元素做op運算,會得到A自己
- 例如:加法的證同元素為0(因為任何元素加上0仍為自己)。乘法的證同元素為1(任何元素乘以1仍為自己)
- 下面這些函式並非STL標準中的一員,但很多STL都實現了它們:
五、關係運算類(Relational)仿函式
- STL內建的“關係運算類仿函式”,支援等於、不等於、大於、大於等於、小於、小於等於六種運算
- 每一個都是二元運算
- 包含如下:
- 等於(equality):equal_to<T>
- 不等於(inequality):not_equal_to<T>
- 大於(greater than):greater<T>
- 大於或等於(greater than or equal):greater_equal<T>
- 小於(less than):less<T>
- 小於或等於(less than or equal):less_equal<T>
- 例如下面的程式碼表示以遞增次序對vector進行排序:
sort(iv.begin(), iv.end(), greater<int>());
原始碼如下:
template<class T> struct equal_to :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x == y; } }; template<class T> struct not_equal_to :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x != y; } }; template<class T> struct greater :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x > y; } }; template<class T> struct less :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x < y; } }; template<class T> struct greater_equal :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x >= y; } }; template<class T> struct less_equal :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x <= y; } };
演示案例
#include <iostream> #include <functional> using namespace std; int main() { equal_to<int> equal_to_obj; not_equal_to<int> not_equal_to_obj; greater<int> greater_obj; greater_equal<int> greater_equal_obj; less<int> less_obj; less_equal<int> less_equal_obj; std::cout << equal_to_obj(3, 5) << std::endl; std::cout << not_equal_to_obj(3, 5) << std::endl; std::cout << greater_obj(3, 5) << std::endl; std::cout << greater_equal_obj(3, 5) << std::endl; std::cout << less_obj(3, 5) << std::endl; std::cout << less_equal_obj(3, 5) << std::endl; std::cout << "**************************" << std::endl; std::cout << equal_to<int>()(3, 5) << std::endl; std::cout << not_equal_to<int>()(3, 5) << std::endl; std::cout << greater<int>()(3, 5) << std::endl; std::cout << greater_equal<int>()(3, 5) << std::endl; std::cout << less<int>()(3, 5) << std::endl; std::cout << less_equal<int>()(3, 5) << std::endl; return 0; }
六、邏輯運算類(Logical)仿函式
- STL內建的“邏輯運算類仿函式”,支援邏輯運算中的And、Or、Not三種運算
- 其中And和Or為二元運算,Not為一元運算
- 包含如下:
- 邏輯運算 And:logical_and<T>
- 邏輯運算Or:logical_or<T>
- 邏輯運算 Not:logical_not<T>
原始碼如下:
template<class T> struct logical_and :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x&&y; } }; template<class T> struct logical_or :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x||y; } }; template<class T> struct logical_not :public binary_function<T, T, bool> { bool operator()(const T& x, const T& y)const { return x!=y; } };
演示案例
#include <iostream> #include <functional> using namespace std; int main() { logical_and<int> and_obj; logical_or<int> or_obj; logical_not<int> not_obj; std::cout << and_obj(true, true) << std::endl; std::cout << or_obj(true, true) << std::endl; std::cout << not_obj(true) << std::endl; std::cout << "**************************" << std::endl; std::cout << logical_and<int>()(true, true) << std::endl; std::cout << logical_or<int>()(true, true) << std::endl; std::cout << logical_not<int>()(true) << std::endl; return 0; }
七、證同(identity)、選擇(select)、投射(project)
- 下面介紹的這些仿函式,都只是將其引數原封不動地傳回。其中某些仿函式對傳回的引數有刻意的選擇,或者可以的忽略
- 之所以不在STL或其他泛型程式中直接使用原本及其簡單的identity、project、select等操作,而要再劃分一層出來,全是為了間接性——間接性是抽象化的重要工具
- C++標準併為包含下面的這幾個仿函式,不過它們常常存在於各個實現品作為內部使用。下面是SGI STL的版本
identity
select1st
select2nd
project1st
project2nd
相關文章
- jQuery 原始碼剖析(一) - 核心功能函式jQuery原始碼函式
- 淺析stl仿函式函式
- 【C++】【原始碼解讀】std::is_same函式原始碼解讀C++原始碼函式
- count 函式原始碼分析函式原始碼
- js的call函式”原始碼”JS函式原始碼
- bind函式polyfill原始碼解析函式原始碼
- STL原始碼剖析——vector容器原始碼
- snabbdom原始碼解析(二) h函式原始碼函式
- 讀 zepto 原始碼之工具函式原始碼函式
- [PHP原始碼閱讀]strlen函式PHP原始碼函式
- Vue原始碼: 建構函式入口Vue原始碼函式
- Vue 原始碼中的工具函式Vue原始碼函式
- 臨時讀原始碼的函式原始碼函式
- ClickHouse原始碼筆記5:聚合函式的原始碼再梳理原始碼筆記函式
- vue原始碼解讀-建構函式Vue原始碼函式
- redux原始碼分析之四:compose函式Redux原始碼函式
- C++中函式指標與函式物件C++函式指標物件
- jQuery 原始碼學習 (三) 回撥函式jQuery原始碼函式
- 【原始碼】Scrollsubplot:subplot函式的升級版原始碼函式
- ONNX Runtime 原始碼閱讀:Graph::SetGraphInputsOutputs() 函式原始碼函式
- c++內建函式物件C++函式物件
- 【原始碼】MATLAB特徵選擇函式庫version 6.2.2018.1原始碼Matlab特徵函式
- vuex 原始碼:深入 vuex 之輔助函式 mapStateVue原始碼函式
- PHP原始碼分析-函式array_merge的”BUG”PHP原始碼函式
- C++ functional庫中的仿函式C++Function函式
- C++ lambda 表示式與「函式物件」(functor)C++函式物件
- call仿函式函式
- C++ 常物件和常函式C++物件函式
- 人人都能懂的Vue原始碼系列(三)—resolveConstructorOptions函式Vue原始碼Struct函式
- Vue原始碼探祕(五)(_render 函式的實現)Vue原始碼函式
- ClickHouse原始碼筆記1:聚合函式的實現原始碼筆記函式
- [PHP原始碼閱讀]strpos、strstr和stripos、stristr函式PHP原始碼函式
- Vue原始碼探究-類初始化函式詳情Vue原始碼函式
- php-src原始碼zend_startup_builtin_functions函式PHP原始碼UIFunction函式
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- PostgreSQL 原始碼解讀(140)- Buffer Manager#5(BufTableInsert函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(150)- PG Tools#2(BaseBackup函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(148)- Storage Manager#4(mdread函式)SQL原始碼函式