Lambda 表達式
Lambda 表達式是 C++11 引入的一種強大的功能,它允許你在代碼中直接定義匿名函數對象。Lambda 表達式可以捕獲上下文中的變量,并在需要時使用它們。它們通常用于簡化代碼,尤其是那些需要傳遞函數對象作為參數的場景(如標準庫中的算法函數)。
1.?Lambda 表達式的語法
Lambda 表達式的基本語法如下:
[capture](parameters) -> return_type { body }
2.?Lambda 表達式的用法
2.1?無捕獲、無參數的 Lambda
最簡單的 Lambda 表達式不捕獲任何變量,也不接受任何參數。
auto lambda = []() {std::cout << "Hello, Lambda!" << std::endl;
};lambda(); // 輸出:Hello, Lambda!
2.2?帶參數的 Lambda
Lambda 表達式可以接受參數,就像普通函數一樣。
auto add = [](int a, int b) {return a + b; };std::cout << add(3, 4) << std::endl; // 輸出:7
2.3?捕獲變量的 Lambda
Lambda 表達式可以通過捕獲列表捕獲上下文中的變量。捕獲方式有以下幾種:
值捕獲:
[x]
,捕獲變量x
的副本。引用捕獲:
[&x]
,捕獲變量x
的引用。捕獲所有變量:
[=]
,捕獲所有變量的副本;[&]
,捕獲所有變量的引用。
int x = 10;
auto lambda = [x]() {std::cout << "x = " << x << std::endl; // 使用捕獲的變量 x
};lambda(); // 輸出:x = 10
如果捕獲變量后修改了變量的值,捕獲方式會影響 Lambda 表達式的行為:
int x = 10;
auto lambda1 = [x]() {x = 20; // 錯誤:不能修改捕獲的變量副本
};auto lambda2 = [&x]() {x = 20; // 正確:修改捕獲的變量引用
};lambda2();
std::cout << x << std::endl; // 輸出:20
2.4?Lambda 作為函數參數
Lambda 表達式常用于作為函數參數,尤其是標準庫中的算法函數。
#include <vector>
#include <algorithm>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用 Lambda 表達式作為參數std::for_each(vec.begin(), vec.end(), [](int x) {std::cout << x << " ";});std::cout << std::endl;// 使用 Lambda 表達式作為謂詞auto isEven = [](int x) {return x % 2 == 0;};std::vector<int> evenVec;std::copy_if(vec.begin(), vec.end(), std::back_inserter(evenVec), isEven);for (int x : evenVec) {std::cout << x << " ";}std::cout << std::endl;return 0;
}
2.5?Lambda 作為成員函數
Lambda 表達式也可以綁定到對象上,從而實現類似成員函數的行為。
class MyClass {
public:int value;MyClass(int v) : value(v) {}void print() const {std::cout << "Value = " << value << std::endl;}
};int main() {MyClass obj(42);// 使用 Lambda 表達式綁定成員函數auto printLambda = [&obj]() {obj.print();};printLambda(); // 輸出:Value = 42return 0;
}
3.?Lambda 表達式的優點
簡潔性:Lambda 表達式允許你直接在需要的地方定義函數對象,避免了定義單獨的函數或函數對象類。
靈活性:Lambda 表達式可以捕獲上下文中的變量,使其在函數體中可用。
性能:Lambda 表達式通常比
std::bind
更高效,因為它們沒有額外的類型擦除開銷。可讀性:Lambda 表達式通常比函數指針或函數對象類更易讀。
過度使用:雖然 Lambda 表達式非常強大,但過度使用可能會使代碼難以理解。
捕獲復雜性:捕獲變量時,需要特別注意捕獲方式(值捕獲或引用捕獲)對變量生命周期的影響。
C++14 引入了 Lambda 捕獲初始化的功能,允許你在捕獲列表中初始化變量。
#include <iostream>int main() {auto lambda = [x = 10, y = 20]() mutable {std::cout << "x = " << x << ", y = " << y << std::endl;x = 30; // 修改捕獲的變量};lambda(); // 輸出:x = 10, y = 20lambda(); // 輸出:x = 30, y = 20
}
[capture]
:捕獲列表,用于捕獲上下文中的變量。捕獲方式可以是值捕獲([x]
)或引用捕獲([&x]
),也可以捕獲所有變量([=]
或[&]
)。(parameters)
:參數列表,與普通函數的參數列表類似。-> return_type
:返回類型(可選)。如果 Lambda 表達式的返回類型可以自動推導,則可以省略。{ body }
:函數體,可以包含任意合法的 C++ 代碼。
C++ 右值引用與移動語義詳解
右值引用和移動語義是 C++11 引入的最重要特性之一,它們極大地提升了 C++ 程序的性能,特別是在處理臨時對象和資源管理方面。
?1. 左值 vs 右值
左值 (lvalue)
- 可以取地址的表達式
- 有持久的狀態(在內存中有固定位置)
- 通常有名字
- 示例:
int a = 10; // a 是左值
int* p = &a; // 可以取地址
右值 (rvalue)
- 不能取地址的臨時表達式
- 通常是即將銷毀的臨時對象
- 沒有名字
- 示例:
10; ? ? ? ? ? ? // 字面量是右值a + b; ? ? ? ? ?// 表達式結果是右值std::string("hello"); // 臨時對象是右值
總結一句話就是能取地址是左值不能取地址是右值
2. 右值引用 (Rvalue Reference)
基本概念
- 使用 `&&` 語法聲明
- 只能綁定到右值
- 主要用途:標識可以被"移動"的資源
int&& rref = 10; // 右值引用
std::string&& sref = std::string("hello");
?重要特性
1. **延長臨時對象生命周期**:右值引用綁定的臨時對象生命周期會延長到引用作用域結束
2. **重載決議**:可以重載函數來區分左值和右值參數
3. 移動語義 (Move Semantics)
核心思想
- 允許"竊取"右值對象的資源(如動態內存),而非深拷貝
- 避免不必要的資源分配和釋放
- 顯著提升性能,特別是對于管理資源的類(如容器、字符串等)
?移動構造函數
class MyClass {
public:// 移動構造函數:參數是右值引用MyClass(MyClass&& other) noexcept {// 轉移資源所有權(例如指針)// noexcept 是一個用于聲明函數不會拋出異常的關鍵字this->ptr = other.ptr;other.ptr = nullptr; // 確保原對象不再擁有資源}private:int* ptr;
};
- 核心特征:
- 參數類型為?
T&&
(右值引用)。 - 通常會 “竊取” 原對象的資源(如指針、文件句柄),并將原對象置為有效但空的狀態。
- 參數類型為?
?4. std::move
作用
- 將左值顯式轉換為右值引用
- 表示對象可以被移動(資源可以被竊取)
- 位于 `<utility>` 頭文件
std::string str = "Hello";
std::string another = std::move(str);
// str 現在處于有效但未指定的狀態
注意事項
1. 被移動的對象處于有效但未指定的狀態(通常為空/null)
2. 不應再使用被移動對象的值,但可以重新賦值或銷毀
3. 不是所有類型都支持移動語義(基本類型移動等同于拷貝)
?5. 完美轉發 (Perfect Forwarding)
?std::forward
- 保持參數原始值類別(左值/右值)
- 用于模板函數中轉發參數
template<typename T>
void wrapper(T&& arg) {// 保持 arg 的原始值類別some_function(std::forward<T>(arg));
}
完美轉發 (Perfect Forwarding)
在 C++ 中,完美轉發(Perfect Forwarding)是一種將參數原封不動地傳遞給另一個函數的技術,同時保留參數的值類別(左值或右值)和常量性
1. 核心概念:為什么需要完美轉發?
假設有一個包裝函數?wrapper
,需要將參數?arg
?傳遞給?func
,同時保留?arg
?的原始屬性(左值 / 右值):
template<typename T>
void func(T&& arg) {// 處理參數 arg
}template<typename T>
void wrapper(T&& arg)//這里是萬能引用而非右值引用
{func(arg); // ? 錯誤:無論arg是左值還是右值,傳遞給func時都會變成左值
}
- 問題:
arg
?作為函數參數,本身是一個左值(即使它被聲明為右值引用?T&&
)。因此,func(arg)
?總是調用?func
?的左值版本。 - 目標:讓?
wrapper
?能夠區分傳入的參數是左值還是右值,并將這種屬性 “完美” 地傳遞給?func
。
2. 完美轉發的實現:std::forward
C++11 引入了?std::forward
(位于?<utility>
?頭文件),用于在轉發參數時保留其原始值類別:
template<typename T>
void wrapper(T&& arg) {func(std::forward<T>(arg)); // ? 完美轉發:保留arg的原始左值/右值屬性
}
- 原理:
std::forward<T>(arg)
?根據?T
?的推導類型,將?arg
?轉換為對應的左值引用或右值引用。- 當?
T
?被推導為左值引用(如?int&
)時,std::forward
?返回左值引用; - 當?
T
?被推導為右值引用(如?int&&
)時,std::forward
?返回右值引用。
3. 萬能引用(Universal Reference)與引用折疊
(1)萬能引用(Universal Reference)
T&&
?在兩種情況下有不同含義:
- 右值引用:明確指定類型時(如?
int&&
)。 - 萬能引用:用于模板類型推導時(如?
template<typename T> void f(T&&)
)。
例如:
template<typename T>
void f(T&& arg); // 這里的 T&& 是萬能引用,可接受左值或右值void g(int&& x); // 這里的 int&& 是右值引用,只能接受右值
(2)引用折疊規則
當涉及多重引用時,C++ 遵循以下規則:
T& &
?折疊為?T&
T& &&
?折疊為?T&
T&& &
?折疊為?T&
T&& &&
?折疊為?T&&
例如:
template<typename T>
void wrapper(T&& arg) {// 當傳入左值時,T 被推導為 int&,則 T&& 為 int& &&,折疊為 int&// 當傳入右值時,T 被推導為 int, 則 T&& 為 int&&
}
4. 示例代碼
(1)基本完美轉發
#include <utility>
#include <iostream>void print(int& x) {std::cout << "左值: " << x << std::endl;
}void print(int&& x) {std::cout << "右值: " << x << std::endl;
}template<typename T>
void wrapper(T&& arg) {print(std::forward<T>(arg)); // 完美轉發
}int main() {int a = 42;wrapper(a); // 傳遞左值,調用 print(int&)wrapper(123); // 傳遞右值,調用 print(int&&)
}
(2)轉發多個參數
template<typename F, typename... Args>
decltype(auto) call(F&& f, Args&&... args) {return std::forward<F>(f)(std::forward<Args>(args)...);
}// 使用示例
int add(int a, int b) { return a + b; }int result = call(add, 3, 4); // 等價于 add(3, 4)
5. 常見應用場景
(1)函數包裝器
template<typename Func>
class Wrapper {
private:Func func;
public:template<typename F>Wrapper(F&& f) : func(std::forward<F>(f)) {} // 完美轉發構造函數template<typename... Args>decltype(auto) operator()(Args&&... args) {return func(std::forward<Args>(args)...); // 完美轉發調用}
};
(2)工廠函數
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}// 使用示例
auto ptr = make_unique<MyClass>(arg1, arg2); // 完美轉發參數到MyClass構造函數
(3)移動語義與完美轉發結合
template<typename T>
void push_back(T&& value) {container.push_back(std::forward<T>(value)); // 保留value的左值/右值屬性
}
6. 注意事項
僅對需要轉發的參數使用?
std::forward
:template<typename T> void f(T&& x) {g(x); // 傳遞左值g(std::move(x)); // 強制轉換為右值(移動語義)g(std::forward<T>(x)); // 完美轉發 }
避免在中間變量上使用完美轉發:
template<typename T> void wrapper(T&& arg) {auto intermediate = std::forward<T>(arg);// ? 錯誤:intermediate是左值func(intermediate); // 總是傳遞左值 }
std::forward
?需要顯式指定模板參數:std::forward<T>(arg); // ? 正確 std::forward(arg); // ? 錯誤:必須指定T
可變參數模板(Variadic Templates)
可變參數模板(Variadic Templates)是 C++11 引入的核心特性之一,它允許模板定義接受任意數量、任意類型的參數,極大地增強了模板的靈活性和通用性。標準庫中的?std::tuple
、std::make_unique
、std::format
?等功能都依賴于可變參數模板實現。
一、核心概念:參數包(Parameter Pack)
可變參數模板的核心是參數包(Parameter Pack),它分為兩種:
- 模板參數包(Template Parameter Pack):表示零個或多個模板參數。
- 函數參數包(Function Parameter Pack):表示零個或多個函數參數。
二、基礎語法
1. 模板參數包的定義
用?typename... Args
(或?class... Args
)聲明模板參數包,其中?Args
?是參數包的名稱(可自定義):
// 模板參數包:Args 表示零個或多個類型
template<typename... Args>
struct MyStruct {};
2. 函數參數包的定義
用?Args&&... args
?聲明函數參數包(結合萬能引用?&&
?可轉發參數值類別):
// 函數參數包:args 表示零個或多個函數參數
template<typename... Args>
void my_func(Args&&... args) {}
3. 完整示例:接受任意參數的函數
// 可變參數模板函數:打印任意數量、任意類型的參數
template<typename... Args>
void print(Args&&... args) { // 后續講解如何“展開”參數包
} // 調用示例:支持任意數量和類型的參數
print(1, "hello", 3.14, std::vector<int>{1,2,3});
三、參數包的展開(Unpacking)
參數包本身是 “打包” 的,無法直接使用,必須通過展開(Unpacking)才能訪問其中的單個元素。C++ 提供了多種展開方式,常用的有以下幾種:
1. 遞歸展開(C++11 起)
遞歸展開是最經典的方式:通過遞歸函數,每次處理參數包中的第一個元素,再對剩余元素遞歸調用,直到參數包為空。
// 遞歸終止條件:處理零個參數(遞歸出口)
void print_recursive() { std::cout << "遞歸結束\n";
} // 遞歸函數:處理第一個參數,剩余參數遞歸傳遞
template<typename First, typename... Rest>
void print_recursive(First&& first, Rest&&... rest) { std::cout << "參數:" << first << "(剩余" << sizeof...(rest) << "個)\n"; print_recursive(std::forward<Rest>(rest)...); // 遞歸展開剩余參數
} // 調用示例
print_recursive(1, "hello", 3.14);
輸出:
參數:1(剩余2個)
參數:hello(剩余1個)
參數:3.14(剩余0個)
遞歸結束
2. 折疊表達式(Fold Expressions,C++17 起)
C++17 引入了折疊表達式,可通過運算符對參數包進行 “批量運算”,無需遞歸,語法更簡潔。
折疊表達式的語法為:(包操作 ... 運算符)
?或?(運算符 ... 包操作)
,支持?+
、*
、&&
、||
、,
?等運算符。
// 示例1:用折疊表達式求和(支持任意數量的數值類型參數)
template<typename... Args>
auto sum(Args&&... args) { return (args + ...); // 折疊表達式:args1 + args2 + ... + argsN
} // 示例2:用折疊表達式打印參數(借助逗號運算符)
template<typename... Args>
void print_fold(Args&&... args) {
// 逗號運算符:先執行打印,再返回0;折疊后等效于 (print(args1), print(args2), ..., 0) (std::cout << ... << args) << "\n";
} // 調用示例
int total = sum(1, 2, 3, 4.5); // 1+2+3+4.5=10.5
print_fold("sum = ", total); // 輸出:sum = 10.5
3. 結合?std::tuple
?和?std::apply
(C++17 起)
通過?std::tuple
?存儲參數包,再用?std::apply
?展開調用函數:
#include <tuple>
#include <functional>// 目標函數:接受3個參數
void func(int a, double b, const std::string& c) { std::cout << a << ", " << b << ", " << c << "\n";
} // 可變參數模板:將參數打包為tuple,再調用apply展開
template<typename F, typename... Args>
void call_with_tuple(F&& f, Args&&... args) { auto tuple_args = std::make_tuple(std::forward<Args>(args)...); // 打包參數 std::apply(std::forward<F>(f), tuple_args); // 展開tuple并調用函數
} // 調用示例
call_with_tuple(func, 10, 3.14, "hello"); // 輸出:10, 3.14, hello
四、與萬能引用和完美轉發結合
可變參數模板常與萬能引用(&&
)和?std::forward
?配合,實現完美轉發(Preserve Value Category),即保持參數的原始值類別(左值 / 右值)。
示例:完美轉發任意參數
// 可變參數模板:轉發參數給目標函數func
template<typename... Args>
void wrapper(Args&&... args) { func(std::forward<Args>(args)...); // 展開參數包并完美轉發
} // 目標函數:重載左值和右值版本
void func(int& x) { std::cout << "左值引用:" << x << "\n"; }
void func(int&& x) { std::cout << "右值引用:" << x << "\n"; } // 調用示例
int a = 10;
wrapper(a); // 傳遞左值:調用func(int&)
wrapper(20); // 傳遞右值:調用func(int&&)
wrapper(std::move(a)); // 傳遞右值:調用func(int&&)
五、模板參數包的推導規則
當調用可變參數模板時,編譯器會自動推導參數包的類型:
- 若傳遞左值(如?
int a;
),推導為左值引用類型(如?int&
); - 若傳遞右值(如字面量?
10
?或?std::move(a)
),推導為非引用類型(如?int
)。
結合萬能引用?&&
?后,參數包會保持原始值類別的信息,配合?std::forward
?即可完美轉發。
六、應用場景
- 通用函數封裝:如?
std::make_unique
、std::thread
?的構造函數,接受任意參數轉發給對象構造函數。 - 容器 / 數據結構:
std::tuple
?存儲任意數量、任意類型的元素;std::variant
?支持多類型備選。 - 格式化與打印:
std::format
?接受任意數量的格式化參數;日志庫的打印函數。 - 元編程:在編譯期遍歷類型列表、計算參數數量等(結合?
sizeof...(Args)
?獲取參數個數)。
類型別名 (using 替代 typedef)
在 C++ 中,類型別名(Type Alias)是為現有類型定義新名稱的機制,用于提高代碼可讀性和可維護性。C++11 引入的?using
?語法是傳統?typedef
?的現代替代品,功能更強大,語法更直觀。
1. 基本語法對比
(1)傳統?typedef
typedef int MyInt; // 為int定義別名MyInt
typedef void (*FuncPtr)(int); // 為函數指針定義別名
typedef std::vector<int> IntVec; // 為vector<int>定義別名
(2)現代?using
?語法
using MyInt = int; // 等價于typedef
using FuncPtr = void (*)(int); // 函數指針別名
using IntVec = std::vector<int>; // 容器別名
關鍵區別:using
?的語法更接近賦值語句,直觀易讀;而?typedef
?的語法更像聲明變量。
2.?using
?的優勢
(1)模板別名(Template Aliases)
using
?支持定義模板別名(Template Aliases),而?typedef
?無法直接實現:
// 傳統typedef:無法直接定義模板別名
template<typename T>
struct VecWrapper {typedef std::vector<T> type; // 需通過嵌套類型定義
};
VecWrapper<int>::type vec; // 使用時需寫::type// 現代using:直接定義模板別名
template<typename T>
using VecAlias = std::vector<T>; // 直接定義別名
VecAlias<int> vec; // 使用更簡潔
(2)函數對象和 lambda 類型
using
?更適合處理復雜的類型,如函數對象和 lambda:
// 函數對象類型別名
using Compare = std::function<bool(int, int)>;// lambda類型別名(需用decltype)
auto lambda = [](int x) { return x * 2; };
using LambdaType = decltype(lambda);
(3)可讀性更強
對于復雜類型(如嵌套模板、函數指針),using
?的語法更清晰:
// 函數指針示例
typedef void (*Callback)(int, char); // 傳統typedef
using Callback = void (*)(int, char); // using更直觀// 嵌套模板示例
typedef std::map<std::string, std::vector<int>> MapType; // 傳統
using MapType = std::map<std::string, std::vector<int>>; // 現代
3. 別名模板(Alias Templates)
using
?允許創建參數化的類型別名,稱為別名模板:
// 為有特定分配器的vector定義別名模板
template<typename T>
using MyVector = std::vector<T, MyAllocator<T>>;// 使用
MyVector<int> vec; // 等價于std::vector<int, MyAllocator<int>>
對比:typedef
?無法直接實現參數化的類型別名,需借助模板和嵌套類型。
4. 作用域和繼承中的差異
(1)在類中使用
using
?和?typedef
?在類中定義類型別名的方式類似,但?using
?更靈活:
class MyClass {
public:// 傳統typedeftypedef int IntType;// 現代usingusing SizeType = std::size_t;// 使用模板別名template<typename T>using Pair = std::pair<T, T>;
};// 使用類中的別名
MyClass::IntType x = 42;
MyClass::Pair<double> p(3.14, 2.71);
(2)繼承中的類型別名
using
?可以更方便地在派生類中引入基類的類型別名:
class Base {
public:using ValueType = int;
};class Derived : public Base {
public:using Base::ValueType; // 引入基類的類型別名(可選)
};
5. 與?auto
?和?decltype
?的結合
using
?可與?auto
?和?decltype
?結合,為復雜類型創建簡潔別名:
// 為lambda類型創建別名
auto add = [](int a, int b) { return a + b; };
using AddType = decltype(add); // AddType是lambda的類型// 為函數返回類型創建別名
using ResultType = decltype(someFunction()); // someFunction返回值的類型
std::function
在 C++ 中,std::function
?是一個通用的多態函數包裝器(位于?<functional>
?頭文件),它可以存儲、復制和調用任何可調用對象(函數、函數指針、成員函數指針、lambda 表達式、仿函數等)。std::function
?是類型安全的,常用于回調機制、事件處理和函數對象的存儲。
1. 基本語法與用法
(1)定義與初始化
#include <functional>// 定義一個接受兩個int并返回int的函數對象
std::function<int(int, int)> add;// 用lambda初始化
add = [](int a, int b) { return a + b; };// 調用
int result = add(3, 4); // 結果為7
(2)存儲不同類型的可調用對象
// 1. 普通函數
int subtract(int a, int b) { return a - b; }
std::function<int(int, int)> op = subtract;// 2. lambda表達式
op = [](int a, int b) { return a * b; };// 3. 函數對象(仿函數)
struct Divide {int _a;int operator()(int a, int b) const { return a / b; }
};
op = Divide();
std::function<int(Divide&)> a = &Divide::_a;
std::cout<<a(1)<<std::endl;// 4. 成員函數指針
struct Adder {int add(int a, int b) { return a + b; }
};
Adder adder;
std::function<int(int, int)> member_op = [&adder](int a, int b) {return adder.add(a, b);
};
2. 成員函數的包裝
包裝成員函數時,需要綁定對象實例(通過 lambda 或?std::bind
):
struct Logger {void log(const std::string& msg) {std::cout << "Log: " << msg << std::endl;}
};
//如果不使用bind或者lambda的話,第一個參數則是類引用,第二個是函數類型
innt main()
{std::function<string(Logger&,string)> lo = &Logger::log
}// 方法1:使用lambda捕獲對象
Logger logger;
std::function<void(const std::string&)> log_func = [&logger](const std::string& msg) {logger.log(msg);
};// 方法2:使用std::bind(C++11起)
#include <functional>
log_func = std::bind(&Logger::log, &logger, std::placeholders::_1);// 調用
log_func("Hello, world!"); // 輸出:Log: Hello, world!
3. 與函數指針的對比
特性 | std::function | 函數指針 |
---|---|---|
可調用對象類型 | 任意(函數、lambda、仿函數、成員函數等) | 僅普通函數和靜態成員函數 |
類型擦除 | 支持(存儲任意可調用對象) | 不支持(類型嚴格匹配) |
狀態存儲 | 支持(如 lambda 的捕獲列表) | 不支持 |
空狀態檢查 | 可通過?operator bool() ?檢查 | 直接與 nullptr 比較 |
性能 | 有少量開銷(堆分配、虛函數調用) | 無額外開銷 |
4. 空狀態與錯誤處理
std::function
?可以為空(未初始化或被賦值為?nullptr
),調用空的?std::function
?會拋出?std::bad_function_call
?異常:
std::function<void()> func; // 默認構造,為空if (!func) { // 檢查是否為空std::cout << "Function is empty!" << std::endl;
}try {func(); // 調用空函數,拋出異常
} catch (const std::bad_function_call& e) {std::cerr << "Error: " << e.what() << std::endl;
}// 賦值為nullptr
func = nullptr;
5. 應用場景
(1)回調函數
// 定義一個接受回調函數的函數
void onEvent(std::function<void(int)> callback) {// 事件發生時調用回調callback(42);
}// 使用lambda作為回調
onEvent([](int code) {std::cout << "Event code: " << code << std::endl;
});
(2)函數注冊表
#include <unordered_map>// 注冊表:字符串映射到函數
std::unordered_map<std::string, std::function<int(int, int)>> op_map;// 注冊函數
op_map["add"] = [](int a, int b) { return a + b; };
op_map["sub"] = [](int a, int b) { return a - b; };// 調用
int result = op_map["add"](3, 4); // 結果為7
(3)延遲執行
// 存儲一個函數供后續執行
std::function<void()> task;// 初始化任務
task = []() {std::cout << "Task executed!" << std::endl;
};// 稍后執行
task();
6. 性能考慮
- 優勢:
std::function
?提供了類型安全和靈活性,適合需要存儲多種可調用對象的場景。 - 劣勢:相比直接調用函數或函數指針,
std::function
?有少量開銷(堆分配、虛函數調用),因此在性能敏感的代碼中需謹慎使用。
智能指針
在 C++ 中,智能指針(Smart Pointer)是一種用于管理動態分配內存的類模板,它能夠自動釋放不再使用的對象,避免內存泄漏。智能指針通過 RAII(資源獲取即初始化)技術,將堆內存的生命周期與對象的生命周期綁定,是現代 C++ 編程的核心工具之一。
一、為什么需要智能指針?
傳統的裸指針(Naked Pointer)存在以下問題:
- 內存泄漏:忘記調用?
delete
?釋放內存。 - 懸空指針:對象已被釋放,但指針仍在使用。
- 重復釋放:多個指針指向同一對象,多次調用?
delete
。
智能指針通過自動管理內存生命周期,解決了這些問題。
二、C++ 標準庫中的智能指針
C++11 引入了三種智能指針(位于?<memory>
?頭文件):
std::unique_ptr
:獨占所有權的智能指針。std::shared_ptr
:共享所有權的智能指針。std::weak_ptr
:弱引用,配合?shared_ptr
?使用,避免循環引用。
三、std::unique_ptr
1. 特性
- 獨占所有權:同一時間只能有一個?
unique_ptr
?指向某個對象。 - 自動釋放:
unique_ptr
?銷毀時,其管理的對象會被自動刪除。 - 不可復制:禁止拷貝構造和賦值,但支持移動語義。
2. 基本用法
#include <memory>// 創建unique_ptr(推薦使用make_unique,C++14起)
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);// 等價于(C++11寫法)
std::unique_ptr<int> ptr2(new int(42));// 訪問對象
std::cout << *ptr1 << std::endl; // 輸出:42// 轉移所有權(通過移動語義)
std::unique_ptr<int> ptr3 = std::move(ptr1); // ptr1變為空
3. 作為函數參數和返回值
// 函數返回unique_ptr
std::unique_ptr<Shape> createCircle() {return std::make_unique<Circle>();
}
std::unique_ptr<Shape> createCircle() {return std::unique_ptr<Circle>();
}// 函數接受unique_ptr(通過移動)
void processShape(std::unique_ptr<Shape> shape) {// ...
}// 使用示例
auto circle = createCircle();
processShape(std::move(circle)); // 轉移所有權到函數
四、std::shared_ptr
1. 特性
- 共享所有權:多個?
shared_ptr
?可以指向同一對象。 - 引用計數:通過引用計數(Reference Counting)記錄有多少個?
shared_ptr
?共享對象。 - 自動釋放:當最后一個?
shared_ptr
?被銷毀時,對象才會被釋放。
2. 基本用法
// 創建shared_ptr(推薦使用make_shared,效率更高)
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);// 拷貝構造,引用計數+1
std::shared_ptr<int> ptr2 = ptr1;// 當前引用計數
std::cout << ptr1.use_count() << std::endl; // 輸出:2// 重置其中一個,引用計數-1
ptr2.reset();
std::cout << ptr1.use_count() << std::endl; // 輸出:1
3. 自定義刪除器
// 自定義刪除器(例如關閉文件)
void fileDeleter(FILE* file) {if (file) fclose(file);
}// 使用自定義刪除器
std::shared_ptr<FILE> file(fopen("test.txt", "r"), fileDeleter);
五、std::weak_ptr
1. 特性
- 弱引用:不控制對象的生命周期,僅觀測?
shared_ptr
?管理的對象。 - 解決循環引用:當?
shared_ptr
?之間形成循環引用時,使用?weak_ptr
?打破循環。 - 必須轉換為?
shared_ptr
?才能使用對象:通過?lock()
?方法獲取?shared_ptr
。
2. 循環引用示例
struct Node {std::shared_ptr<Node> next; // 循環引用:導致內存泄漏~Node() { std::cout << "Node destroyed" << std::endl; }
};// 修復循環引用:使用weak_ptr
struct Node {std::weak_ptr<Node> next; // 弱引用,不增加引用計數~Node() { std::cout << "Node destroyed" << std::endl; }
};// 使用示例
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2; // node2的引用計數為1
node2->next = node1; // 若next為shared_ptr,則形成循環;改為weak_ptr后無循環
3. 使用?weak_ptr
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared; // 弱引用shared// 檢查對象是否存在并使用
if (auto locked = weak.lock()) { // 轉換為shared_ptrstd::cout << *locked << std::endl; // 輸出:42
}
六、智能指針的選擇原則
場景 | 推薦智能指針 |
---|---|
獨占資源所有權 | std::unique_ptr |
共享資源所有權 | std::shared_ptr |
避免循環引用 | std::weak_ptr |
作為類成員變量 | 優先?unique_ptr |
函數返回動態對象 | unique_ptr ?或?shared_ptr |
緩存 / 觀察者模式 | weak_ptr |
七、常見注意事項
避免混合使用裸指針和智能指針:
int* raw = new int(42); std::shared_ptr<int> ptr1(raw); // 危險:raw可能被多處管理 std::shared_ptr<int> ptr2(raw); // 錯誤:重復釋放同一內存
優先使用工廠函數創建智能指針:
// 推薦:自動推導類型,異常安全 auto ptr = std::make_unique<MyClass>(args...);// 不推薦:手動new,可能導致內存泄漏 std::unique_ptr<MyClass> ptr(new MyClass(args...));
shared_ptr
?不要管理棧上對象:int x = 42; std::shared_ptr<int> ptr(&x); // 錯誤:ptr會嘗試delete棧上對象
八、智能指針與數組
C++11 起,智能指針支持管理數組:
// unique_ptr管理數組(自動調用delete[])
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
arr[0] = 42;// shared_ptr管理數組(需顯式指定刪除器)
std::shared_ptr<int> arr2(new int[10], [](int* p) { delete[] p; });
?bind函數
在 C++ 里,std::bind
是一個很重要的函數模板,其作用是把可調用對象(像函數、函數指針、成員函數指針、函數對象等)和它的參數綁定起來,進而生成一個新的可調用對象。下面為你詳細介紹它的功能、使用方法以及相關注意要點。
功能概述
std::bind
主要具備以下功能:
- 能固定可調用對象的部分參數,也就是所謂的 “部分函數應用”。
- 可以對參數的傳遞順序進行調整。
- 借助占位符,能靈活地控制參數的傳遞時機。
基礎語法
std::bind
的基本使用格式如下:
auto newCallable = std::bind(可調用對象, 參數1, 參數2, ...);
其中:
可調用對象
指的是要綁定的函數或者其他可調用實體。參數1, 參數2, ...
是傳遞給可調用對象的參數,這里面可以包含占位符(例如std::placeholders::_1
)。
占位符的運用
在std::bind
中,占位符(如_1
,?_2
,?_3
, ...)用來表示新可調用對象的參數位置。這些占位符都定義在std::placeholders
命名空間里。下面通過例子來說明:
#include <iostream>
#include <functional>using namespace std::placeholders;void print(int a, int b) {std::cout << "a = " << a << ", b = " << b << std::endl;
}int main() {// 把print函數的第一個參數綁定為10,第二個參數使用占位符_1auto f = std::bind(print, 10, _1);f(20); // 輸出:a = 10, b = 20// 交換參數順序auto g = std::bind(print, _2, _1);g(100, 200); // 輸出:a = 200, b = 100return 0;
}
綁定成員函數
在綁定類的成員函數時,需要把對象實例(或者對象指針、引用)作為第一個參數,示例如下:
#include <iostream>
#include <functional>class Calculator {
public:int add(int a, int b) { return a + b; }
};int main() {Calculator calc;auto add = std::bind(&Calculator::add, &calc, _1, _2);std::cout << add(3, 4) << std::endl; // 輸出:7return 0;
}
參數傳遞方式
- 值傳遞:默認情況下,參數會以值傳遞的方式被保存。
- 引用傳遞:如果想以引用的方式傳遞參數,需要使用
std::ref
或者std::cref
。
void increment(int& x) { ++x; }int main() {int value = 10;auto func = std::bind(increment, std::ref(value));func();std::cout << value << std::endl; // 輸出:11return 0;
}
與 lambda 表達式的對比
雖然std::bind
和 lambda 表達式都能用于捕獲參數,但它們各有特點:
std::bind
:適合進行簡單的參數綁定,不過在處理復雜邏輯時可讀性會變差。- lambda 表達式:語法更加簡潔直觀,而且功能更為強大,所以在現代 C++ 編程中更受推薦。
注意要點
- 要使用
std::bind
,需要包含<functional>
頭文件。 - 占位符的命名空間是
std::placeholders
。 - 當綁定的參數涉及動態資源時,要留意對象的生命周期管理問題。