在 Cocos2d-x、現代 C++ 框架與業務代碼中,std::function 無處不在。作為 C++11 引入的**通用多態函數包裝器**,它實現了對函數指標、仿函數、Lambda、成員函數等異構可調用物件的**型別擦除**,提供統一、型別安全的調用介面。本文深度解析其核心機制、使用模式、轉換規則、效能特徵與工程最佳實踐。
template<class R, class... Args>
class function<R(Args...)>;
| 傳統方案 | 缺陷 | std::function 方案 |
|---|---|---|
| 函數指標 | 無法儲存捕獲變數的 Lambda、仿函數、成員函數 | 統一包裝所有可調用物件,型別安全,支援賦值、拷貝、移動 |
| 仿函數類 | 每種簽名需定義專屬類,無法統一儲存 | |
| void* + 強制轉型 | 型別不安全,易崩潰,維護困難 | |
| 模板參數化 | 編譯期多態,無法運行期切換行為,二進制膨脹 |
在運行期「擦除」具體可調用物件的具體類型,僅保留**調用簽名**(返回類型 + 參數列表),透過內部虛函數表或函數指標實現動態分發。類似 Java 的 Function<T, R> 或 C# 的 Func<T, R>。
#include <functional>
#include <iostream>
using namespace std;
// 1. 普通函數
int add(int a, int b) { return a + b; }
// 2. Lambda 表達式(無捕獲可隱式轉函數指標)
auto lambda = [](int a, int b) { return a + b; };
// 3. 仿函數
struct Adder {
int operator()(int a, int b) const { return a + b; }
};
// 4. 類靜態成員函數
struct Math {
static int add(int a, int b) { return a + b; }
};
// 5. 類成員函數(需配合 bind/this)
struct Calculator {
int add(int a, int b) { return a + b; }
};
int main() {
// 統一介面:function<返回類型(參數列表)>
function<int(int, int)> func;
// 1. 普通函數
func = add;
cout << "普通函數: " << func(1, 2) << endl; // 3
// 2. Lambda
func = lambda;
cout << "Lambda: " << func(3, 4) << endl; // 7
// 3. 仿函數
func = Adder();
cout << "仿函數: " << func(5, 6) << endl; // 11
// 4. 靜態成員函數
func = Math::add;
cout << "靜態成員: " << func(7, 8) << endl; // 15
// 5. 成員函數(必須綁定 this)
Calculator calc;
func = bind(&Calculator::add, &calc, placeholders::_1, placeholders::_2);
cout << "成員函數: " << func(9, 10) << endl; // 19
// 6. 成員函數 + Lambda 捕獲 this(C++14 通用 Lambda)
func = [&calc](int a, int b) { return calc.add(a, b); };
cout << "Lambda捕獲: " << func(11, 12) << endl; // 23
return 0;
}
可調用物件 F 可賦值給 std::function<R(Args...)> 僅當:
function 的每個參數類型 Args_i 能隱式轉換為 F 對應參數類型F 的返回類型能隱式轉換為 Rfunction<void(int)> f1 = [](long x) { }; // OK: int → long 隱式轉換
function<long(int)> f2 = []() { return 42; }; // OK: 無參 → 忽略參數,int → long
// function<int(double)> f3 = [](int x) { return x; }; // 錯誤:double 無法隱式轉 int(窄化)
| 操作 | 語法 | 說明 |
|---|---|---|
| 構造/賦值 | function<Sig> f = callable; | 模板轉換構造,支援移動語義 |
| 調用 | f(args...) | 類函數調用語法,參數型別檢查 |
| 空狀態檢查 | if (f) / if (!f) / f == nullptr | 唯一支援的比較操作,**不可比較相等性** |
| 清空 | f = nullptr; / f = function<Sig>(); | 釋放持有物件,恢復空狀態 |
| 目標類型查詢 | f.target_type() | 返回 type_info,若為空則 typeid(void) |
| 提取原始物件 | f.target<T>() | 若持有類型為 T 返回指標,否則 nullptr |
| 交換 | swap(f1, f2) | 常數時間交換內部存儲 |
function 需深拷貝內部物件,移動則轉移所有權標準庫實現通常內建緩衝區(如 16-32 bytes),直接存儲小型可調用物件(函數指標、無捕獲 Lambda、小型仿函數),避免堆分配。超過閾值則落回堆分配。
| 調用方式 | 相對開銷 | 適用場景 |
|---|---|---|
| 直接函數調用 | 1.0x (基準) | 效能極敏感熱路徑 |
| 函數指標 | 1.0-1.2x | 靜態回調、C 介面互操作 |
| std::function (SBO) | 1.5-3.0x | 通用回調、插件架構、配置驅動 |
| std::function (堆分配) | 3-10x+ | 大型捕獲 Lambda、複雜仿函數 |
| 虛函數介面 | 1.2-2.0x | 面向對象多態、介面隔離 |
void process(const function<Sig>& f) 避免拷貝func = std::move(lambda) 轉移所有權std::pmr::polymorphic_allocator (C++17)class EventSystem {
unordered_map<string, vector<function<void(const Event&)>>> callbacks_;
public:
void subscribe(string event, function<void(const Event&)> cb) {
callbacks_[std::move(event)].push_back(std::move(cb));
}
void publish(const string& event, const Event& e) {
auto it = callbacks_.find(event);
if (it != callbacks_.end())
for (auto& cb : it->second) cb(e);
}
};
using Command = function<void()>;
class CommandQueue {
queue<Command> q_;
mutex mtx_;
public:
void push(Command cmd) { lock_guard<mutex> l(mtx_); q_.push(std::move(cmd)); }
void executeAll() {
while (true) {
Command cmd;
{ lock_guard<mutex> l(mtx_); if (q_.empty()) break; cmd = std::move(q_.front()); q_.pop(); }
if (cmd) cmd();
}
}
};
using SortStrategy = function<void(vector<int>&)>;
SortStrategy quickSort = [](auto& v) { sort(v.begin(), v.end()); };
SortStrategy stableSort = [](auto& v) { stable_sort(v.begin(), v.end()); };
SortStrategy customSort = [](auto& v) { /* 自定義邏輯 */ };
void processData(vector<int>& data, const SortStrategy& strategy) {
strategy(data); // 運行期決策排序算法
}
class Controller {
function<void(int, int)> onClick_;
public:
template<class T>
void setClickHandler(T* obj, void (T::*method)(int, int)) {
onClick_ = bind(method, obj, placeholders::_1, placeholders::_2);
}
void triggerClick(int x, int y) { if (onClick_) onClick_(x, y); }
};
| 陷阱 | 現象 | 解決方案 |
|---|---|---|
| 比較相等性 | f1 == f2 編譯錯誤 | 僅支援與 nullptr 比較;需比較邏輯請用 target_type() + target<T>() |
| 空調用崩潰 | 未檢查空狀態直接調用 → std::bad_function_call | 調用前 if (func) func(args); |
| 循環引用 | function 捕獲 shared_ptr<this> 導致記憶體洩漏 | 捕獲 weak_ptr,調用時 lock() 檢查有效性 |
| 隱式轉換驚喜 | 參數/返回類型隱式轉換導致精度損失或截斷 | 明確指定 function 簽名匹配目標,避免窄化轉換 |
| 移動後狀態 | 移動賦值後源物件為空 | 理解移動語義:f2 = std::move(f1) 後 f1 為空 |
std::move_only_function (C++23):僅可移動,不可拷貝,更輕量,適合獨佔回調場景std::function_ref (提案中):非擁有型引用包裝器,零開銷,替代 function 做參數傳遞std::pmr::function)std::function 是 C++ 型別擦除技術的集大成者,以微小的運行期開銷換取極大的設計靈活性:
void* 強轉災難決策矩陣:
std::reference_wrapper掌握 std::function 的能力邊界與效能特徵,是現代 C++ 架構設計的核心技能之一。善用它構建靈活可擴展的系統,並在效能關鍵路徑果斷選擇替代方案。