C++(STL原始碼):37---仿函式(函式物件)原始碼剖析

江南、董少發表於2020-04-14

一、仿函式概述

  • 仿函式(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

相關文章