35

Boost Signals庫中,它們正在重載()運算符。爲什麼重載operator()?

這是C++中的約定嗎?對於回調等?

我在一位同事的代碼中看到了這個(他恰好是一個大的Boost粉絲)。在所有的提升善良中,這隻會讓我感到困惑。

任何有關此超載的原因的洞察?

+1

相關http://stackoverflow.com/questions/356950/c-functors-and-their-uses? – Konrad 2010-11-29 17:10:13

回答

100

重載operator()的主要目標之一是創建函子。一個仿函數就像一個函數,但它的優點是它是有狀態的,這意味着它可以保持數據在調用之間反映它的狀態。

下面是一個簡單的仿例如:

struct Accumulator 
{ 
    int counter = 0; 
    int operator()(int i) { return counter += i; } 
} 
... 
Accumulator acc; 
cout << acc(10) << endl; //prints "10" 
cout << acc(20) << endl; //prints "30" 

仿函數用巨資泛型編程使用。許多STL算法都是以一種非常通用的方式編寫的,因此您可以將自己的函數/函子插入到算法中。例如,std :: for_each算法允許您對範圍的每個元素應用操作。它可以實現類似的東西:

template <typename InputIterator, typename Functor> 
void for_each(InputIterator first, InputIterator last, Functor f) 
{ 
    while (first != last) f(*first++); 
} 

你看,既然它是由功能參數化這個算法是非常通用的。通過使用operator(),該函數可讓您使用函數指針或函數指針。下面是顯示這兩種可能性的例子:

void print(int i) { std::cout << i << std::endl; } 
...  
std::vector<int> vec; 
// Fill vec 

// Using a functor 
Accumulator acc; 
std::for_each(vec.begin(), vec.end(), acc); 
// acc.counter contains the sum of all elements of the vector 

// Using a function pointer 
std::for_each(vec.begin(), vec.end(), print); // prints all elements 

關於你對運營商的問題()超載,以及是有可能。只要遵守方法重載的基本規則(例如僅在返回類型上重載是不可能的),就可以完美地編寫一個有多個括號運算符的函子。

1

另一位同事指出,它可能是一種將functor對象僞裝成函數的方法。例如,這樣的:

my_functor(); 

是真的:

my_functor.operator()(); 

這是否意味着這樣的:

my_functor(int n, float f){ ... }; 

可用於重載這個呢?

my_functor.operator()(int n, float f){ ... }; 
+0

您的最後一行根本不是操作符重載。它需要是:「.operator()(int n,float f)」,這在你第一次看到它時看起來很混亂。你可以象這樣的其他函數那樣重載這個「函數調用操作符」,但是你不能用你指定的非操作符重載來重載它。 – altruic 2008-11-25 14:23:58

+0

@altruic,好點。只是修復它。 – JeffV 2008-11-25 14:26:43

+0

你的第二行是錯誤的,它實際上是「my_functor.operator()();」。 my_functor.operator()是方法引用,而第二組()表示調用。 – eduffy 2008-11-25 14:38:27

4

仿函數不是一個函數,所以你不能重載它。
雖然operator()的重載用於創建「函子」 - 可以像函數一樣調用的對象,但您的同事是正確的。結合預期「類似功能」參數的模板,這可能非常強大,因爲對象和功能之間的區別變得模糊。

正如其他海報所說:函子優於普通函數,因爲它們可以有狀態。這種狀態可用於一次迭代(例如計算容器中所有元素的總和)或多次迭代(例如,查找滿足特定條件的多個容器中的所有元素)。

18

它允許類作爲一個函數。我曾經在一個日誌類中使用它,其中的調用應該是一個函數,但我想要這個類的額外好處。

所以是這樣的:

logger.log("Log this message"); 

變成這樣:

logger("Log this message"); 
3

開始使用std::for_eachstd::find_if等更多的時候在你的代碼,你會看到爲什麼它的方便有()運算符的重載能力。它還允許函數和任務具有明確的調用方法,這些方法不會與派生類中的其他方法的名稱發生衝突。

2

函子就像函數指針一樣。它們通常旨在可複製(如函數指針),並以與函數指針相同的方式進行調用。主要的好處是,當你有一個可以與模板函數一起工作的算法時,可以內聯對operator()的函數調用。但是,函數指針仍然是有效的函子。

5

許多人都回答說,它是一個函數,沒有告訴一個大函數爲什麼一個函子比一個普通的舊函數更好。

答案是一個仿函數可以有狀態。考慮一個求和功能 - 它需要保持運行總量。

class Sum 
{ 
public: 
    Sum() : m_total(0) 
    { 
    } 
    void operator()(int value) 
    { 
     m_total += value; 
    } 
    int m_total; 
}; 
+0

這並不能解釋爲什麼需要隱藏它是一個對象並僞裝成一個函數的事實。 – JeffV 2008-11-25 14:39:30

2

然而,我可以看到的一個優點是operator()的簽名在不同類型之間的外觀和行爲是相同的。如果我們有一個擁有成員方法報告的類Reporter(..),然後是另一個具有成員方法write(..)的類Writer,那麼如果我們想要同時使用這兩個類,則必須編寫適配器一些其他系統的模板組件。所有它會關心的是傳遞字符串或你有什麼。如果不使用操作符()重載或寫特殊類型的適配器,你不能做的東西一樣

T t; 
t.write("Hello world"); 

因爲T有一個要求,即有它接受任何隱含澆注料爲const char的成員函數稱爲寫* (或者是const char [])。這個例子中的Reporter類沒有這個,所以使用Reporter的T(模板參數)將無法編譯。

不過,據我可以看到這將與不同類型的

T t; 
t("Hello world"); 

的工作,雖然,它仍然明確要求類型T有這樣的運營商定義的,所以我們還是有要求的個人T. ,我不認爲它與通用函數太奇怪了,因爲它們通常被使用,但我更願意看到這種行爲的其他機制。在C#等語言中,您只需傳入一個委託即可。我不太熟悉C++中的成員函數指針,但我可以想象你可以在那裏實現相同的行爲。

除了合成糖的行爲我真的沒有看到運算符重載執行這些任務的優點。

我相信有更多的人知道這些人比我有更好的理由,但我想我會爲你們其他人分享我的意見。

1

其他職位已經做了很好的描述operator()如何工作以及爲什麼它可以有用。

我最近一直在使用一些代碼,使得operator()的使用非常廣泛。重載此運算符的一個缺點是某些IDE因此變得效率較低。在Visual Studio中,通常可以右鍵單擊方法調用以轉到方法定義和/或聲明。不幸的是,VS並不足以爲operator()調用建立索引。特別是在覆蓋整個運算符()的複雜代碼中,可能很難找出在哪裏執行哪段代碼。在幾種情況下,我發現我必須運行代碼並通過它來追蹤實際運行的內容。

相關問題