Function Object 的優點
基本上,function object 和 function pointer 可以做的事其實差不多,不過和 function pointer 相比,function object 是有一些優點的∼一般來說,function object 的優點主要是兩點:一個是他可以透過 inline 來提升效能,其次是他可以在 function 內部來記錄狀態。
前者除了程式碼本身的寫法外,主要更取決於編譯器的最佳化能力,之後 Heresy 應該會用 MSVC10 和 gcc 測試一下 function pointer 和 function object 的效能,而在這邊就暫時跳過了。
而在狀態紀錄的功能方面,由於 function object 本身就可以是一個自行定義的類別物件,所以其實內部要有多少變數用來記錄狀態,其實是相當自由的!雖然 function pointer 也不是完全沒有辦法同樣的事,但是相對之下,使用 function object 應該還是會比較簡單的∼
這邊舉個例子,假如我們定義了一個有三項的 vector 結構 Vector3:
struct Vector3
{
Vector3( const int& a, const int& b, const int& c )
{
m_iaData[0] = a;
m_iaData[1] = b;
m_iaData[2] = c;
}
int m_iaData[3];
};
而如果又建立了一個 vector< Vector3 > 的資料,那想要用 STL 的 sort() 來針對某一項排序的話,可以建立一個比較大小用的 function object:
class Comparsion
{
public:
int idx;
Comparsion( const int& i ) : idx( i ){};
bool operator() ( const Vector3& v1, const Vector3& v2 ) const
{
return v1.m_iaData[idx] < v2.m_iaData[idx];
}
};
這個 class 裡面除了定義了比較大小用的 operator() 外,也另外宣告出了一個成員變數 idx、以及對應的建構子,用來指定要比較的項目;這樣要使用的時候,只要透過 constructor 來指定要比較的項目就可以了∼像下面的寫法,就可以讓 STL 的 sort() 使用我們自己定義的 Comparsion 來針對 Vector3 的第一項做比較、排序了∼
vector< Vector3 > v3;
...
sort( v3.begin(), v3.end(), Comparsion(1) );
此外,其實使用 function object 也更能符合物件導向的概念,像是如果透過 operator overload 的話,也可以讓一個 function object 裡有多個不同的 operator(),進而讓單一的 function class 有更多的功能。
STL 提供的 Function Object
除了可以自己定義 function object 外,C STL 的 <functional> 這個 header 檔裡,也有提供一些已經定義好的 function object 可以直接拿來用;包含了三類,共十五個 template function object:
- 算術(Arithmetic)
- plus、minus、multiplies、divides、modulus、negate
- 比較(Comparison)
- equal_to、not_equal_to、greater、less、greater_equal、less_equal
- 邏輯計算(Logical)
- logical_and、logical_or、logical_not
而每個 function object 的內容實際上都非常地簡單,在這邊就不詳列了;有興趣的人也可以參考 cplusplus.com 文章裡「operator classes」的說明。
STL Binder
除了這些預先定義好的 function object 外,STL 在 <functional> 裡,也還有提供一些其他功能,可以幫助程式開發者使用這些 function object;像是「binder」、「negators」、「conversors」都算是這類的東西。
要怎麼用呢?首先,STL 有定義出:只有一個參數的「一元函式」(unary_function)和有兩參數的「二元函式」(binary_function)。而 STL 裡不同的演算法,視需求的不同,要傳入的 function object 可能會是一元的(例如 for_each()),也有可能是二元的(例如 sort())。
像上面所列的 STL function object 裡,除了 negate 和 logical_not 是一元函式外,其他的都是二元函式。而一元函式因為本身的特性,本來就不太可能變成二元函數來使用;但是有的時候,我們可能會希望透過指定其中一項參數的值,來把二元函式變成一個一元函式來使用,這時候就可以透過 STL 的 binder 來做了∼
STL 的 binder 有 bind1st() 和 bind2nd() 兩個函式,前者是用來指定二元函式的第一個參數的值,後者則是用來指定第二個參數的值。這邊 Heresy 用 count_if() 來做簡單的使用範例:
struct GreaterThan
{
int x;
GreaterThan( int value ) : x( value ){};
bool operator() ( int value )
{
return value > x;
}
};
vector<int> v;
...
int num = count_if( v.begin(), v.end(), GreaterThan( 4 ) );
像是上面的例子,我們可以透過 count_if() 以及自己定義的 GreaterThan 來計算 vector v 裡面,值大於某個數的項目個數;不過實際上,我們也可以透過 STL 的 binder 和 greater 來做到同樣的事,而不用自己去定義這個 function object。寫法如下:
num = count_if( v.begin(), v.end(), bind2nd( greater<int>(), 4 ) );
其中,「bind2nd( greater<int>(), 4 )」就是透過將 STL 內建的 greater 這個內建的二元函式的第二個參數指定成「4」,讓他變成一個「判斷值是否大於 4」的一元函式;如此,就可以給 count_if() 這類需要一元函式的演算法來使用了∼
不過,如果要把 binder 可以套用在自己定義的 function object 上的話,那 function object 的 clasee 就必需要繼承 binary_function 這個 class,如此才能使用 binder 來做轉換。如果需要更強大、自由的 bind 功能的話,可能就要使用 Boost C Libraries 裡的 bind 函式庫了∼而這部份就以後有時間再說了。
使用物件本身的函式來當 Function Object
除了 binder 外,STL 也有提供所謂的「conversors」,可以把 function pointer 或是物件本身的成員函式轉換成 function object、來在 STL 的演算法裡使用。而 conversors 這部份的函式有三種,分別是:ptr_fun()、mem_fun() 以及 mem_fun_ref()。
其中,ptr_fun() 是可以將 function pointer 轉換成為 function object;而他本身是兩個 function,分別對應到一元函式和二元函式。不過 Heresy 不打算講這個,有興趣的請自行參考 cpluplus.com 的文章。
而 mem_fun() 和 mem_fun_ref() 則是可以將物件的成員函式轉換成 function object 來使用;如果以套用到 STL 的 for_each() 來說的話,就是會去呼叫指定的資料範圍裡每一項資料的特定函示了!下面是一個簡單的例子:
vector< string > vString;
//...
for_each( vString.begin(), vString.end(), mem_fun_ref( &string::clear ) );
這個例子會透過 for_each() 去呼叫 vString 裡每一個字串的 string::clear() 函式,藉此把 vString 裡的每一個字串都清空。也就是相當於下面的程式:
for( vector<string>::iterator it = vString.begin(); it != vString.end(); it )
(*it).clear();
而 mem_fun() 和 mem_fun_ref() 兩者的差別,則在於 mem_fun() 是透過 pointer 的形式來呼叫,而 mem_fun_ref() 則是透過 reference 的形式來呼叫;要用哪一個就要視自己的資料形式而定了∼如果資料本身是指標的話,就用 mem_fun(),而資料是物件的話,就使用 mem_fun_ref()。下面就是使用 pointer 版的例子:
vector< string* > vStrPtr;
//...
for_each( vStrPtr.begin(), vStrPtr.end(), mem_fun( &string::clear ) );
這一篇對於 function object 的基本介紹大概就先寫到這了∼下一篇,還會再針對 TR1 對於 function object 的一些擴充,來做進一步的說明的∼