跳到主要內容

C++ Standard Library 讀書筆記: Function Object

我讀 C++ Standard Library: a Tutorial and Reference 後的筆記,主要內容來自 5.9小節,整個第八章,以及一些我自己的感想。

STL Algorithms

C++ STL Algorithms 裡一系列操作容器的函數,我一直以來都覺得這系列函數異常的難用。例如萬年範例 -- for_each 逐一打印容器的元素:
void print_int( int i ) {
    cout << i << endl;
}
for_each(v.begin(), v.end(), print_int); //逐一印出元素

乍看之下簡潔,但是這 for_each 有個致命的缺點,就是沒辦法傳入額外參數。例如我想對集合內的每個數值都加上一個固定值,用 for 迴圈再直覺不過:
int value = 5;
vector<int>::iterator it;
for(it=v.begin(); it!=v.end(); ++it)
    *it = *it + value;

這麼簡單的程式,想要改寫成for_each版本,馬上碰壁:
void add_value( int & i ) {
    i = i + ???;  // add something?
}

int main() {
    vector<int> v;
    for_each(v.begin(), v.end(), add_value);
}
問號的地方只能放常數或者全域變數,而兩個選項都很爛,我真正想要的是從 main 裡面傳入一個變數。所以很長一段時間我把 for_each 封印起來,乖乖自己寫 for 迴圈。

Function Object

直到最近,我才明瞭到 function object 可能是這個惱人的問題的答案。

所謂的 function object 就是一個object,但是實做了operator() 。它實際上是物件,但可以當作函數來呼叫。要發揮整個 STL Algorithms 的威力,就一定要瞭解 function object。萬年打印範例用funciton object 來實作就會長這樣子 :
class PrintInt{
    void operator() (int i) const {
        cout << i;
    }
};

for_each(v.begin(), b.end(), PrintInt());

PrintInt 就是一個 function object,它有實作operator()。 我們可以宣告一個物件變數 PrintInt p;  對他做好像函數呼叫的動作 p(); 而此時實際上呼叫的是 PrintInt::operator() 這個成員函數。 function object 最大的好處就是裡面可以定義成員變數,維護內部的狀態,而一般的函式做不到。我們回頭拿它來解決剛剛的惱人問題,就可以明白我在說什麼。這兒定義了一個很簡單的小物件AddValue,因為AddValue有實作operator() ,所以它是一個function object。
class AddValue {
    int value;
public:
    AddValue(int v) { value = v; }
    void operator()(int &i) { i = i+value; }
};

int main() {
    vector<int> v;
    for_each(v.begin(), v.end(), AddValue(5));
}

把AddValue物件當作參數傳入, 此時 for_each 針對集合元素的每次 function call 實際上是呼叫 AddValue::operator() ,而我們希望加上去的固定值,被當做 function object 的成員變數存起來了。這樣就巧妙的解決了額外參數的問題。

AddValue add10(10), add30(30); //建立許多function object在不同狀況使用
for_each(v.begin(), v.end(), add10);
for_each(v.begin(), v.end(), add30);

求平均值

「求平均值」是比較進階的例子,用 for_each 來計算整數集合的平均值。我們利用了 for_each 最後會回傳該 function object 的特性,把元素的總和(sum) 跟元素個數(num_element) 都紀錄在的function object的成員變數中,最後取平均值 :
class MeanValue {
    int sum;
    int num_element;
public:
    MeanValue() { sum = num_element = 0;}
    void operator() (int i) { sum += i; num_element++; }
    double value() { return (double)sum / (double)num_element); }
};

int main() {
    vecter<int> v;
    MeanValue m = for_each(v.begin(), v.end(), MeanValue());
    cout << m.value();
}

Function Object的優點

Function Object主要有三個優點
  1. Function Object 比一般函數聰明,它可以維護內部的狀態,記錄呼叫過程中發生的事。
  2. 通常 Function Object 的效能比函數指標好,因為比較容易最佳化。
  3. Function Object 可以當作 template 參數使用,一般的函數不行。

預先定義好的Function Object

除了自己寫 function object 以外,STL已經定義好了一系列常用的 function object 。這裡舉兩個簡單的範例,欲知詳情可以參閱 C++ Reference "functional"
int myint[] = {20,30,10,50,40};
vector<int> v(myint, myint+5);

sort(v.begin(), v.end(), less<int>()); //排序由小到大
sort(v.begin(), v.end(), greater<int>()); // 排序由大到小

remove_if(v.begin(), v.end(), bind2nd( greater<int>(), 40)); //移除所有大於40的元素

留言

這個網誌中的熱門文章

讀書心得: 你以為你以為的就是你以為的嗎?

當初想要接觸哲學的時候,挑上的第一本書就是「你以為你以為就是你以為的嗎?」,因為書名太好玩了。讀完後發現內容也一樣好玩,整本書的形式有點像坊間流行的心理測驗小書,每章的開頭都要讀者先做一組題目,後面接著就對你的答案做一番分析。 我自己的讀後感是,恩,這些題目對一個以前從未接觸過哲學的人來說太過犀利,第一次作答的時候,每翻過一頁都好像在呼自己巴掌,臉頰很燙。 「你以為你以為就是你以為的嗎?」書名念起來很拗口,但是很貼切,因為這些題目為的是要檢查我們腦袋內的想法是否一致,在邏輯上有沒有BUG。 我舉個例子,書裡有道題目「只要不傷害他人,任何人都有權自由追求自己的目標」要讀者回答同不同意,我認為這句話聽起來相當合理,所以勾了同意。過了幾題後,出現另一道題目是「為個人吸食而持有毒品的行為應予除罪化」,這次我的直覺是毒品這麼危險,怎麼可以除罪化呢,馬上勾不同意。 但是,我沒有發現這是刻意安排的陷阱,因為這兩句話其實講的是同一回事。 單純個人持有毒品,不散佈也不販賣,就不算傷害他人,那他就應該有自由追求自己的「吸食毒品」目標的權利,畢竟他只有傷害自己呀。這敘述聽起來有點危險,不過我必須承認一開始的確想的不夠清楚,我以為第一句話是真理,但馬上被反例打了自己的臉。 再舉一個例子,首先是「對藝術品的評斷,純粹是個人品味的問題」,接著是「米開朗基羅是史上數一數二的偉大藝術家」,這牽涉到評斷藝術的標準,不過你只能認同兩句話的其中之一。 書中我最有興趣的是「神明DIY工作室」與「信仰殺戮戰場」,這兩章擺明了直衝基督徒而來。當中有些問題圍繞著以下的敘述,如果你同意「神是全知、全能、又全然慈愛」,那該怎麼解釋世界上發生的許多苦難呢? 比如說被南亞海嘯淹沒的小女孩? 如果神沒辦法消除這些苦難,祂就不是全能。如果神沒辦法事先知道創造出來的世界會有這些苦難,祂就不是全知。如果神明知道有苦難,也有能力去掉,但是卻故意不做,那祂就不是全然慈愛。 我思考後的結論是,全然慈愛的神並不等於神希望世上的苦難越少越好,這些苦難都是在祂的允許下發生的。 書裡指出了一個基督徒的通病,被問倒了之後就嚷嚷「你不知道神是超越人所理解的嗎 」,但回頭又馬上賦予神非常明確地人的屬性。後來我也理解到這些尖銳的問題並不是故意要為難我對神的看法,而是逼迫我去反思一些比較深層的宗教議題,就像我...

UVa 10125 Sumsets

解題策略 這題的解法很直接,要找d=a+b+c 用四層迴圈下去跑a,b,c,d就好了 XDDDD 我犯了幾個錯誤 要找最大符合d (意思是數列中可能出現好幾個符合要求的解),所以迴圈應該由最大元素往下找,第一個找到的解就是答案,我一開始由最小元素往上遞增尋找,拿了WA。 沒找到解就回傳0,殊不知0也有可能是解: 0 = -5 + 3 + 2,這裡也吃了一個WA,所以我後來改回傳 INT_MAX 作為無解。 這題有負值,所以 -5 = -10 + -2 + 7 ,這樣算一組合法的解。 是比較需要小心的地方。 官網論壇上的(a+b)=(d-c)法,方法複雜很多,卻沒有比較快。