//無 comp 引數 void sort (RandomAccessIterator first, RandomAccessIterator last); //有 comp 引數 void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);顯然僅從使用語法上看,它們唯一的區別在於,第 2 種多了一個 comp 引數。
本質上講,comp 引數用於接收使用者自定義的函數,其定義的方式有 2 種,既可以是普通函數,也可以是函數物件。例如:事實上,對於 STL 標準庫中的每個演算法,只要使用者需要自定義規則,該演算法都會提供有帶 comp 引數的語法格式。
#include <iostream> // std::cout #include <algorithm> // std::sort #include <vector> // std::vector //以普通函數的方式實現自定義排序規則 inline bool mycomp(int i, int j) { return (i < j); } //以函數物件的方式實現自定義排序規則 class mycomp2 { public: bool operator() (int i, int j) { return (i < j); } }; int main() { std::vector<int> myvector{ 32, 71, 12, 45, 26, 80, 53, 33 }; //呼叫普通函數定義的排序規則 std::sort(myvector.begin(), myvector.end(), mycomp); //呼叫函數物件定義的排序規則 //std::sort(myvector.begin(), myvector.end(), mycomp2()); for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) { std::cout << *it << ' '; } return 0; }程式執行結果為:
12 26 32 33 45 53 71 80
要知道,函數物件可以理解為偽裝成函數的物件,根據以往的認知,函數物件的執行效率應該不如普通函數。但事實恰恰相反,即便如上面程式那樣,將普通函數定義為更高效的行內函式,其執行效率也無法和函數物件相比。注意,為了提高執行效率,其函數都定義為行內函式(在類內部定義的函數本身就是行內函式)。至於為什麼行內函式比普通函數的執行效率高,可閱讀《C++ inline行內函式》一文。
那麼,是什麼原因導致了它們執行效率上的差異呢?以 mycomp2() 函數物件為例,其 mycomp2::operator() 也是一個行內函式,編譯器在對 sort() 函數進行範例化時會將該函數直接展開,這也就意味著,展開後的 sort() 函數內部不包含任何函數呼叫。通過在 4 個不同的 STL 平台上,對包含 100 萬個 double 型別資料的 vector 容器進行排序,最差情況下使用函數物件的執行效率要比普通行內函式高 50%,最好情況下則高 160%。
std::sort(myvector.begin(), myvector.end(), mycomp);並不是真正地將 mycomp 傳遞給 sort() 函數,它傳遞的僅是一個指向 mycomp() 函數的指標。當 sort() 函數被範例化時,編譯器生成的函數宣告如下所示:
std::sort(vector<int>::iterator first, vector<int>::iterator last, bool (*comp)(int, int));可以看到,引數 comp 只是一個指向函數的指標,所以 sort() 函數內部每次呼叫 comp 時,編譯器都會通過指標產生一個間接的函數呼叫。
也正是基於這個原因,C++ sort() 函數要比 C 語言 qsort() 函數的執行效率更高。讀者可能會問,程式中 comp() 函數也是行內函式,為什麼 C++ 不像函數物件那樣去處理呢?具體原因我們無從得知,事實上也沒必要關心,也許是編譯器開發者覺得這種優化不值得去做。除了效率上的優勢之外,相比普通函數,以函數物件的方式自定義規則還有很多隱藏的優勢。例如在某些特殊情況下,以普通函數的形式編寫的程式碼看似非常合理,但就是無法通過編譯,這也許是由於 STL 標準庫的原因,也許是編譯器缺陷所至,甚至兩者都有可能。而使用函數物件的方式,則可以有效避開這些“坑”,而且還大大提升的程式碼的執行效率。