C++11 引入 Lambda 表達式(Lambda Expression)是現代 C++ 最具影響力的語言特性之一,它將函數式編程思想帶入 C++,極大簡化了仿函數編寫、STL 算法調用、回調註冊、異步編程等場景。本文系統講解 Lambda 完整語法、捕獲列表語義、mutable 修飾符、泛型 Lambda (C++14)、模板 Lambda (C++20)、常見陷阱與最佳實踐。
[capture-list] (params) mutable constexpr -> ret-type { body }
| 組件 | 名稱 | 必要性 | 說明 |
|---|---|---|---|
[capture-list] | 捕獲列表 | ✅ 必須(可為空 []) | 指定外部變數捕獲方式,[] 為 Lambda 引入符 |
(params) | 參數列表 | ⭕ 可省略(無參時連同括號) | 同普通函數參數,支援預設值、引用、右值引用 |
mutable | 可變修飾符 | ⭕ 可省略 | 移除 operator() 的 const 資格,允許修改值捕獲變數 |
constexpr | 常量表達式 | ⭕ C++17 可省略 | 指定 Lambda 可用於常量表達式上下文 |
-> ret-type | 尾置返回類型 | ⭕ 可省略(單一 return 自動推導) | 複雜返回類型、多 return 路徑、遞歸 Lambda 需顯式指定 |
{ body } | 函數體 | ✅ 必須 | 可使用參數、捕獲變數、局部變數、靜態變數、全域變數 |
| 語法 | 模式 | 說明 | 生命週期 |
|---|---|---|---|
[var] | 值捕獲 | 複製變數 var 到閉包對象 | 閉包構造時複製,隨閉包銷毀 |
[&var] | 引用捕獲 | 存儲變數 var 的引用 | 引用外部變數,**必須確保外部變數存活** |
[=] | 預設值捕獲 | 值捕獲所有自動儲存期變數(含 this) | 同上 |
[&] | 預設引用捕獲 | 引用捕獲所有自動儲存期變數(含 this) | 同上,風險較高 |
[this] | 值捕獲 this | 捕獲 this 指標(C++17 後不建議,改用 *this) | 物件生命週期綁定 |
[*this] | 值捕獲 *this | 複製整個物件到閉包(C++17) | 閉包擁有獨立副本 |
int a = 1, b = 2, c = 3, d = 4;
[=, &b, &c] { /* a,d 值捕獲; b,c 引用捕獲 */ }
[&, a, c] { /* b,d 引用捕獲; a,c 值捕獲 */ }
// C++14 初始化捕獲:在閉包中引入新變數
[int x = a + b] { return x * 2; } // x 僅存在於閉包中
// 移動捕獲 (C++14):將唯一所有權資源移入閉包
auto up = make_unique<Widget>();
auto func = [ptr = move(up)] { ptr->doWork(); }; // up 移入閉包,外部變空
class Widget {
int value = 42;
public:
// C++11/14:捕獲 this 指標(隱含引用語義)
auto badCapture() { return [this] { return value; }; } // 危險:this 可能懸空
// C++17:值捕獲 *this(複製整個物件)
auto goodCapture() { return [*this] { return value; }; } // 安全:擁有副本
// C++20:可簡寫為 [=, this] 但語義仍為值捕獲 *this
auto cpp20Capture() { return [=, this] { return value; }; }
};
operator() **預設為 const**const 成員函數**不能修改非靜態、非 mutable 數據成員**mutable 移除 operator() 的 const 資格,允許修改值捕獲變數int val = 0;
// 錯誤:預設 const operator() 不能修改值捕獲變數
// auto f1 = [=]() { val = 3; }; // 編譯錯誤
// 正確:mutable 允許修改閉包內副本
auto f2 = [=]() mutable { val = 3; }; // 修改的是閉包內副本
f2();
cout << val << endl; // 輸出 0(外部 val 未變)
// 引用捕獲無需 mutable 即可修改外部變數
auto f3 = [&]() { val = 4; }; // 修改外部 val
f3();
cout << val << endl; // 輸出 4
// 引用捕獲 + mutable:合法但無意義(引用本身不可變,但可修改引用對象)
auto f4 = [&]() mutable { val = 5; };
f4();
cout << val << endl; // 輸出 5
// [=]() mutable { val = 3; } 等價於:
struct Lambda {
int val; // 值捕獲 → 非靜態數據成員
Lambda(int v) : val(v) {}
void operator()() const { /* 錯誤:const 不能修改 val */ }
void operator()() { val = 3; } // mutable → 非 const 版本
};
// C++14:auto 參數 → 模板化 operator()
auto add = [](auto a, auto b) { return a + b; };
cout << add(1, 2) << endl; // 3 (int)
cout << add(1.5, 2.5) << endl; // 4.0 (double)
cout << add(string("a"), "b") << endl; // "ab" (string)
// 等價於:
struct {
template<typename T, typename U>
auto operator()(T a, U b) const { return a + b; }
} add_struct;
// C++20:顯式指定模板參數
auto vecPrinter = []<typename T>(const vector<T>& v) {
for (const auto& e : v) cout << e << ' ';
cout << endl;
};
vector<int> vi = {1,2,3};
vector<string> vs = {"a","b","c"};
vecPrinter(vi); // 1 2 3
vecPrinter(vs); // a b c
// 結合概念約束 (C++20)
auto addNumbers = []<std::integral T>(T a, T b) { return a + b; };
// addNumbers(1.5, 2.5); // 編譯錯誤:double 不滿足 integral
vector<int> v = {5, 2, 8, 1, 9, 3};
// 排序:自定義比較
sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // 降序
// 查找:條件謂詞
auto it = find_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
// it 指向 8
// 轉換:一元操作
vector<int> squares(v.size());
transform(v.begin(), v.end(), squares.begin(), [](int x) { return x * x; });
// 累加:二元操作
int sum = accumulate(v.begin(), v.end(), 0, [](int a, int b) { return a + b; });
// 移除-擦除慣用法
v.erase(remove_if(v.begin(), v.end(), [](int x) { return x < 5; }), v.end());
// v 保留 ≥5 的元素
// 延遲計算昂貴操作
auto expensive = [] {
cout << "Computing...\n";
return 42;
};
// 只有調用時才執行
cout << "Before\n";
int result = expensive();
cout << "After: " << result << endl;
class EventSystem {
function<void(int)> callback_;
public:
void setCallback(function<void(int)> cb) { callback_ = move(cb); }
void trigger(int code) { if (callback_) callback_(code); }
};
EventSystem sys;
int localState = 100;
// Lambda 捕獲局部狀態註冊回調
sys.setCallback([localState](int code) mutable {
cout << "Code: " << code << ", State: " << ++localState << endl;
});
sys.trigger(1); // Code: 1, State: 101
sys.trigger(2); // Code: 2, State: 102
using Strategy = function<int(int, int)>;
Strategy add = [](int a, int b) { return a + b; };
Strategy mul = [](int a, int b) { return a * b; };
Strategy custom = [](int a, int b) { return a * a + b * b; };
void compute(int a, int b, const Strategy& s) {
cout << s(a, b) << endl;
}
compute(3, 4, add); // 7
compute(3, 4, mul); // 12
compute(3, 4, custom); // 25
// C++14:需 std::function 打破循環依賴
function<int(int)> fib = [&](int n) {
return n <= 1 ? n : fib(n-1) + fib(n-2);
};
// C++20:可直接遞歸(需顯式返回類型)
auto fib2 = []<typename Self>(Self&& self, int n) -> int {
return n <= 1 ? n : self(self, n-1) + self(self, n-2);
};
cout << fib2(fib2, 10) << endl; // 55
| 陷阱 | 現象 | 解決方案 |
|---|---|---|
| 引用捕獲懸空 | Lambda 存活超出捕獲變數作用域 | 值捕獲、shared_ptr、延長外部變數生命週期、或用 *this 複製物件 |
| 隱式捕獲 this | [=] 捕獲 this 指標,物件銷毀後調用崩潰 | C++17 用 [*this] 值捕獲副本;或明確 [this] 並文檔警告 |
| 移動捕獲語法 | [ptr = move(up)] 必須在捕獲列表初始化 | C++14 初始化捕獲語法,不可寫 [move(up)] |
| 泛型 Lambda 返回類型 | 多 return 路徑類型不一致導致推導失敗 | 顯式 -> common_type 或統一返回類型 |
| std::function 儲存開銷 | Lambda 賦值給 function 觸發型別擦除、堆分配 | 熱路徑用模板參數化或 auto 參數傳遞,避免 function |
| 遞歸 Lambda 無限展開 | 編譯期遞歸導致模板實例化爆炸 | 用 std::function 或 C++20 遞歸 Lambda |
[] 大小為 1 (空類優化);捕獲變數增加對應成員std::function 包裝,直接用模板或 auto&& 轉發shared_ptr 或移動捕獲 [ptr = move(obj)][]<typename T>(T x) { ... } 顯式模板參數[this]<-> auto 簡化成員 Lambda 寫法Lambda 表達式是現代 C++ 表達力飛躍的核心:
[cap](params){body} 一行替代仿函數類auto 參數、C++20 模板參數、概念約束this 捕獲是頭號 Bug 來源std::function 有成本,熱路徑避免決策矩陣:
精通 Lambda 的捕獲語義、mutable 機制、泛型演進與工程模式,是現代 C++ 開發者從「會用」進階到「善用」的關鍵里程碑。建議在實際專案中大膽替換舊式仿函數,體驗函數式風格帶來的代碼簡潔與表達力提升。