C++11的整理筆記

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. 注意事項

  1. 僅對需要轉發的參數使用?std::forward

    template<typename T>
    void f(T&& x) {g(x);                  // 傳遞左值g(std::move(x));       // 強制轉換為右值(移動語義)g(std::forward<T>(x)); // 完美轉發
    }
    
  2. 避免在中間變量上使用完美轉發

    template<typename T>
    void wrapper(T&& arg) {auto intermediate = std::forward<T>(arg);// ? 錯誤:intermediate是左值func(intermediate);  // 總是傳遞左值
    }
    

  3. std::forward?需要顯式指定模板參數

    std::forward<T>(arg);  // ? 正確
    std::forward(arg);     // ? 錯誤:必須指定T

可變參數模板(Variadic Templates)

可變參數模板(Variadic Templates)是 C++11 引入的核心特性之一,它允許模板定義接受任意數量、任意類型的參數,極大地增強了模板的靈活性和通用性。標準庫中的?std::tuplestd::make_uniquestd::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?即可完美轉發。

六、應用場景

  1. 通用函數封裝:如?std::make_uniquestd::thread?的構造函數,接受任意參數轉發給對象構造函數。
  2. 容器 / 數據結構std::tuple?存儲任意數量、任意類型的元素;std::variant?支持多類型備選。
  3. 格式化與打印std::format?接受任意數量的格式化參數;日志庫的打印函數。
  4. 元編程:在編譯期遍歷類型列表、計算參數數量等(結合?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)存在以下問題:

  1. 內存泄漏:忘記調用?delete?釋放內存。
  2. 懸空指針:對象已被釋放,但指針仍在使用。
  3. 重復釋放:多個指針指向同一對象,多次調用?delete

智能指針通過自動管理內存生命周期,解決了這些問題。

二、C++ 標準庫中的智能指針

C++11 引入了三種智能指針(位于?<memory>?頭文件):

  1. std::unique_ptr:獨占所有權的智能指針。
  2. std::shared_ptr:共享所有權的智能指針。
  3. 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

七、常見注意事項

  1. 避免混合使用裸指針和智能指針

    int* raw = new int(42);
    std::shared_ptr<int> ptr1(raw); // 危險:raw可能被多處管理
    std::shared_ptr<int> ptr2(raw); // 錯誤:重復釋放同一內存
    
  2. 優先使用工廠函數創建智能指針

    // 推薦:自動推導類型,異常安全
    auto ptr = std::make_unique<MyClass>(args...);// 不推薦:手動new,可能導致內存泄漏
    std::unique_ptr<MyClass> ptr(new MyClass(args...));
    
  3. 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
  • 當綁定的參數涉及動態資源時,要留意對象的生命周期管理問題。

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/bicheng/88668.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88668.shtml
    英文地址,請注明出處:http://en.pswp.cn/bicheng/88668.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    MS1826+MS9332 4K@30Hz HD4×2視頻分割器

    MS1826MS9332是一款支持4K30Hz分辨率的HD42視頻分割器方案。支持四路HD輸入兩路HD輸出&#xff0c;最高支持4K30Hz分辨率。該方案具有Scaler、OSD、畫面分割、無縫切換、淡入淡出及旋轉等功能。該方案現已實現量產&#xff0c;并提供完善的技術支持&#xff0c;適用于各類高清視…

    用 MATLAB 模擬傳染病傳播:從 SI 模型到 SIS 模型的可視化之旅

    在公共衛生研究中&#xff0c;數學模型是理解傳染病傳播規律的重要工具。通過數值模擬&#xff0c;我們能直觀看到 “易感人群” 和 “感染人群” 隨時間的變化趨勢&#xff0c;甚至能預測疫情發展的關鍵節點。今天就帶大家用 MATLAB 實現兩個經典的傳染病模型 ——SI 模型和SI…

    Ruby如何采集直播數據源地址

    在當今數字化的時代&#xff0c;實時獲取并處理信息變得尤為重要。特別是在體育賽事、新聞報道等領域&#xff0c;及時獲取最新的直播數據源對于提升用戶體驗至關重要。本文將介紹如何使用Ruby語言來采集特定網站的數據源地址 一、準備工作 首先&#xff0c;確保你的環境中已…

    【fitz+PIL】PDF圖片文字顏色加深

    文章目錄0 引言1 解決思路及流程1.1 思路1.2 代碼實現2 完整代碼與效果3 總結0 引言 沒錯&#xff0c;這是連續劇。女友對上一篇【fitzOpenCV】去除PDF圖片中的水印得到的去水印效果很滿意&#xff0c;于是問我可不可以再幫她處理一下另一個PDF文件&#xff0c;我二話不說答應…

    tp8.0\jwt接口安全驗證

    環境&#xff1a;php8.3\tp8.1\firebase-jwt6.1app\middleware\JwtAuth<?php namespace app\middleware;use app\common\library\JwtHandler; use think\Request; use think\facade\Env;class JwtAuth {public function handle(Request $request, \Closure $next){// 獲取當…

    ReactNative【實戰系列教程】我的小紅書 5 -- 文章詳情(含輪播圖 ImageSlider,點亮紅心動畫 Heart,嵌套評論等)

    最終效果 安裝依賴 npm i dayjs用于對時間進行格式化 必備組件 輪播圖 ImageSlider https://blog.csdn.net/weixin_41192489/article/details/149224510 點亮紅心動畫 Heart components/Heart.tsx import AntDesign from "expo/vector-icons/AntDesign"; import …

    嗶哩嗶哩第三方TV-BBLL最新版

    —————【下 載 地 址】——————— 【?本章下載一】&#xff1a;https://pan.xunlei.com/s/VOUtUcaymd9rpgurgDKS9pswA1?pwdp76n# 【?本章下載二】&#xff1a;https://pan.xunlei.com/s/VOUtUcaymd9rpgurgDKS9pswA1?pwdp76n# 【百款黑科技】&#xff1a;https://uc…

    用YOLOv5系列教程(1)-用YOLOv5輕松實現設備狀態智能監控!工業級教程來了

    用YOLOv5輕松實現設備狀態智能監控&#xff01;工業級教程來了設備運維革命&#xff1a;15分鐘教會你的攝像頭看懂指示燈狀態工業現場各種設備狀態指示燈是工程師的"眼睛"——紅燈報警、綠燈運行、黃燈待機。但人工巡檢耗時費力&#xff0c;關鍵故障容易漏檢&#xf…

    筆記-分布式計算基礎

    Distributed Computing 劃分數據并行&#xff08;DataParallelism&#xff09;將數據分為n份&#xff0c;發送到n個GPU上&#xff0c;每個GPU上都存在一個完整的大模型 缺點&#xff1a; 模型太大Pipeline Parallelism&#xff08;串行的&#xff09;將模型做split,每個GPU負責…

    Android Studio 2024,小白入門喂飯級教程

    一、下載Android Studio 1、從安卓官網安卓官網下載Android Studio。 ? ? 二、安裝Android Studio 1、雙擊android-studio-2024.3.2.15-windows.exe。 ? ?? ? ? ? ? 三、新建工程 1、雙擊桌面圖標? 打開Android Studio。 ? 選“Empty Views Activity…

    AI智能體 | 使用Coze制作一鍵生成單詞洗腦循環視頻,一天批量生成100條視頻不是夢!(附保姆級教程)

    目錄 一、整體工作流設計 二、制作工作流 2.1 開始節點 2.2 大模型-單詞 2.3 大模型_圖像生成 2.4 輸出 2.5 圖像生成_1 2.6 輸出_2 2.7 畫板_2 2.8 文本處理 2.9 輸出_3 2.10 speech_synthesis_1x5 2.11 get_audio_duration_1 2.12 代碼 2.13 get_audio_durati…

    質量屬性場景(Quality Attribute Scenario)深度解析

    本質定義:質量屬性場景(Quality Attribute Scenario)是精確描述軟件系統質量要求的結構化方法,通過標準化的場景模板將抽象的質量屬性轉化為可測量、可驗證的具體行為描述,為架構設計提供客觀評估基準。 一、質量屬性場景核心結構 1. 六元組標準模板 #mermaid-svg-AGbvPVRu…

    Java_Springboot技術框架講解部分(一)

    首先講解一下&#xff1a;Java技術棧中&#xff0c;目前Spring Boot在國內的關注趨勢也日漸超過Spring。Spring Boot是Spring家族中的一個全新的框架&#xff0c;它用來簡化Spring應用程序的創建和開發過程。采用Spring Boot可以非常容易和快速的構建基于Spring框架的應用程序&…

    從OpenMV到執行器:當PID算法開始“調教”舵機

    如果到現在還不會驅動舵機——朋友&#xff0c;電賽的元器件清單每年都在對你“明示”&#xff0c;二維云臺都快成祖傳考題了&#xff01;補課&#xff1f;現在&#xff01;立刻&#xff01;&#xff08;當然&#xff0c;如果你臉皮夠厚&#xff0c;也可以私信騷擾作者&#xf…

    xml映射文件的方式操作mybatis

    映射文件 在Java spring中使用mybatis有兩種方式&#xff0c;一種是注釋的方式&#xff0c;一種是xml映射文件的方式。在簡單的功能需求可以使用注釋&#xff0c;方便簡潔。而在大的功能邏輯上&#xff0c;更推薦使用xml映射文件&#xff0c;方便管理且結構清晰。 首先xml文件結…

    Carla自動駕駛仿真_快速安裝與運行Carla

    大家好&#xff0c;我是橙子&#xff0c;今天給大家介紹Carla的基礎安裝和使用 目錄 1.Carla介紹 2.Carla的安裝與使用 3.Carla0.9.15安裝包下載&#xff1a; ?編輯 4.運行Demo 5.運行一個簡單場景&#xff1a; 6.相關資源 1.Carla介紹 Carla 是一個開源的自動駕駛仿…

    遠程登錄docker執行shell報錯input is not a terminal問題

    背景 最近要遠程去k8s docker里面獲取信息&#xff0c;于是&#xff0c;寫了一個如下的命令&#xff0c;執行完之后&#xff0c;報錯了。 ssh 192.168.100.2 sudo crictl exec -it xxx.docker /usr/bin/lscpu --online --extended錯誤信息如下&#xff1a; time“2025-07-11T21…

    使用FastAdmin框架開發二

    繼上一篇使用FastAdmin框架開發-CSDN博客教程 部署完項目后我們就可以邊開發邊調試了 在開發前我們可以先做一些基本設置 安裝成功后會生成一個項目后臺的地址http://域名/VrHGtzlfMB.php/index/login&#xff0c;后臺入口文件是隨機生成的&#xff0c;當然我們也可以根據我…

    【DB2】load報錯SQL3501W、SQL3109N、SQL2036N

    最近老遇到遷移測試LOAD時報錯&#xff0c;如圖所示但是換成import又可以看描述是說load的內容將不會進入備份暫掛狀態balbala… 下面的錯誤是說ixf文件無效 這里一直以為是SQL3501W的問題&#xff0c;去各種研究load參數和db2set里面的load參數&#xff0c;各種調整都不行 又以…

    YOLO家族內戰!v5/v8/v10誰才是你的真命天子?(附保姆級選擇指南)

    在目標檢測領域&#xff0c;YOLO系列始終是工業部署與學術研究的焦點。從風靡全網的YOLOv5&#xff0c;到全面升級的YOLOv8&#xff0c;再到突破性能瓶頸的YOLOv10——每一次迭代都帶來全新可能。作為開發者&#xff0c;究竟該選哪一代&#xff1f;本文用千字長文對比表格為你徹…