2014-10-05 55 views
3

我的確研究了函子的整個概念,但不幸的是我無法理解函子相對於典型函數的真正優勢。C++函子的優勢 - 持有狀態

根據一些學術腳本,仿函數可以保持狀態不同於函數。 任何人都可以用一些簡單易懂的例子來詳細說明這個問題嗎?

我真的不明白爲什麼典型的常規功能不能做到這一點。我真的很抱歉這種新手問題。

+0

不確定我是否同意在此處進行投票。這是一個合法的編程問題。 – par 2014-10-05 17:02:37

+0

已閱讀[這裏](http://stackoverflow.com/questions/356950/c-functors-and-their-uses) – adam10603 2014-10-05 17:03:15

+0

這是一個C++的缺陷;並非所有的語言都受到它的影響 – molbdnilo 2014-10-05 17:04:34

回答

2

作爲一個非常簡單的演示,讓我們考慮一個快速排序。我們選擇一個值(通常稱爲「數據透視表」),並將輸入集合分爲比較小於主數據的數據集以及比較大於或等於主數據點的數據集。。

標準庫已經有std::partition可以做分區本身 - 將集合分爲滿足指定條件的那些項目和那些沒有的項目。所以,要做分割,我們只需要提供一個合適的謂詞。

在這種情況下,我們需要一個簡單的比較,如:return x < pivot;。儘管每次傳遞樞軸值都很困難。 std::partition只是從集合中傳遞一個值,並詢問:「這是否通過了你的測試?」您無法告訴std::partition當前的樞軸值是什麼,並且在調用它時將它傳遞給您的例程。當然可以這樣做可以(例如,Windows中的許多枚舉函數以這種方式工作),但它變得非常笨拙。

當我們調用std::partition時,我們已經選擇了樞軸值。我們想要的是一種方法...將該值綁定到將傳遞給比較函數的參數之一。一個真正醜陋的方式做到這一點是通過一個全局變量「通行證」吧:

int pivot; 

bool pred(int x) { return x < pivot; } 

void quick_sort(int *begin, int *end) { 
    if (end - begin < 2) 
     return; 

    pivot = choose_pivot(begin, end); 

    int *pos = std::partition(begin, end, pred); 
    quick_sort(begin, pos); 
    quick_sort(pos, end); 
} 

我真的希望我不必指出,我們寧可不使用全局此如果我們能幫到的話。避免它的一個相當簡單的方法是創建一個函數對象。我們通過當前樞紐值,當我們創建對象,並將其存儲在對象的值狀態:

class pred { 
    int pivot; 
public: 
    pred(int pivot) : pivot(pivot) {} 

    bool operator()(int x) { return x < pivot; } 
}; 

void quick_sort(int *begin, int *end) { 
    if (end-begin < 2) 
     return; 

    int pivot = choose_pivot(begin, end); 

    int *pos = std::partition(begin, end, pred(pivot));   
    quick_sort(begin, pos); 
    quick_sort(pos, end); 
} 

這增加了額外的代碼一點點,但是作爲交換,我們已經消除了一個使用全局 - 一個相當合理的交流。

當然,從C++ 11開始,我們仍然可以做得更好一些 - 該語言添加了「lambda表達式」,可以爲我們創建一個非常類似的類。利用這一點,我們的代碼看起來是這樣的:

void quick_sort(int *begin, int *end) { 
    if (end-begin < 2) 
     return; 

    int pivot = find_pivot(begin, end); 

    auto pos = std::partition(begin, end, [pivot](int x) { return x < pivot; }); 
    quick_sort(begin, pos); 
    quick_sort(pos, end); 
} 

這改變了語法我們使用指定類/創建函數對象,但它仍然是幾乎相同的基本理念爲前面的代碼:對編譯器用構造函數和operator()生成一個類。我們將括在方括號中的值傳遞給構造函數,並且(int x) { return x < pivot; }基本上變成operator()的主體,該類別爲。

這使得代碼更容易編寫更容易閱讀 - 但它並沒有改變我們創建對象,「捕獲」構造函數中的某些狀態,並使用重載operator()作比較。

當然,比較恰好是我們需要排序的東西。它一般使用lambda表達式和函數對象,但我們當然不限於此。舉另一個例子,讓我們考慮「正常化」一批雙打。我們要找到最大的一個,然後除以該集合中的每個值,所以每個項目在0.0〜1.0的範圍內,但都保持相同的比率對方,因爲他們以前有:

double largest = * std::max_element(begin, end); 
std::for_each(begin, end, [largest](double d) { return d/largest; }); 

在這裏我們再次有幾乎相同的模式:創建一個存儲一些相關狀態的函數對象,然後重複應用該函數對象的operator()來完成真正的工作。


  1. 我們可以分成比,而不是小於或等於大。或者我們可以創建三組:小於,等於,大於。後者可以提高許多副本存在的效率,但目前我們真的不在乎。
  2. 關於lambda表達式的知識還有很多,不僅僅是這個 - 我簡化了一些事情,完全忽略了我們目前不關心的其他事情。