仿函數是 C++ 泛型編程的基石,它讓物件擁有函數般的呼叫語法,並能攜帶狀態、支援型別推導、無縫融入 STL 演算法。本文梳理 STL 兩大基底類別的區別、實戰範例,並引入 Loki 庫的 Functor 與 Typelist,帶你窺探模板遞歸的極致之美。
| 基底類別 | 參數個數 | 適用場景 | 典型成員型別 |
|---|---|---|---|
std::unary_function<Arg, Result> | 1 | 單引數演算法(如 find_if, transform) | argument_type, result_type |
std::binary_function<Arg1, Arg2, Result> | 2 | 雙引數演算法(如 sort, accumulate) | first_argument_type, second_argument_type, result_type |
繼承這些基底類別不強制,但提供統一的型別別名,方便演算法內部萃取參數/回傳型別(C++11 起已棄用,改用 std::invoke_result 等特徵)。
template <typename T>
class MeetsThreshold : public std::unary_function<Widget, bool> {
const T threshold_;
public:
explicit MeetsThreshold(const T& t) : threshold_(t) {}
bool operator()(const Widget& w) const {
return w.value() > threshold_;
}
};
// 用法:std::find_if(v.begin(), v.end(), MeetsThreshold<int>(42));
struct WidgetNameCompare : public std::binary_function<Widget, Widget, bool> {
bool operator()(const Widget& lhs, const Widget& rhs) const {
return lhs.name() < rhs.name();
}
};
// 用法:std::sort(v.begin(), v.end(), WidgetNameCompare());
C++11 起,Lambda 表達式已成為最常用的仿函數形式:
auto cmp = [](const Widget& a, const Widget& b) { return a.name() < b.name(); };
std::sort(v.begin(), v.end(), cmp);
需攜帶狀態、跨編譯單元傳遞或類型擦除時,改用 std::function<bool(const Widget&, const Widget&)>。
Andrei Alexandrescu 的 Loki 庫實現了通用的 Functor,支援:
其核心機制是 Typelist(型別清單)——一個編譯期遞歸結構:
template <class Head, class Tail> struct Typelist { typedef Head Head; typedef Tail Tail; };
struct NullType {}; // 終止符
配合模板特化與遞歸,實現參數包的展開、型別轉換、函數簽名匹配等編譯期計算。這正是「現代 C++ 設計」中模板元編程的經典範例,建議下載 Loki 源碼研讀 Functor.h 與 Typelist.h。
std::function。unary_function/binary_function(C++17 移除),改用型別特徵。const 正確:operator() 標記 const。std::ref 避免拷貝開銷。從 STL 簡潔的基底類別,到 Loki 龐大的模板機器,仿函數見證了 C++ 泛型編程從「型別萃取」走向「編譯期計算」的演進。掌握其精髓,能讓你在架構決策時遊刃有餘。