C++11的整理笔记

发布于:2025-07-13 ⋅ 阅读:(19) ⋅ 点赞:(0)

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 = 42

    return 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 = 20
    lambda(); // 输出: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:
    // 传统typedef
    typedef int IntType;
    
    // 现代using
    using 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_ptr
    std::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,第二个参数使用占位符_1
    auto 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 = 100

    return 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; // 输出:7
    return 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; // 输出:11
    return 0;
}

与 lambda 表达式的对比

虽然std::bind和 lambda 表达式都能用于捕获参数,但它们各有特点:

  • std::bind:适合进行简单的参数绑定,不过在处理复杂逻辑时可读性会变差。
  • lambda 表达式:语法更加简洁直观,而且功能更为强大,所以在现代 C++ 编程中更受推荐。

注意要点

  • 要使用std::bind,需要包含<functional>头文件。
  • 占位符的命名空间是std::placeholders
  • 当绑定的参数涉及动态资源时,要留意对象的生命周期管理问题。

    网站公告

    今日签到

    点亮在社区的每一天
    去签到