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;本文用千字长文对比表格为你彻…