文章目录
智能指针
shared_ptr
std::shared_ptr 是 C++11 引入的智能指针(定义在 <memory> 头文件中),用于共享对象的所有权并自动管理内存,避免手动 new/delete 导致的内存泄漏。
核心原理:引用计数
std::shared_ptr 通过引用计数(reference count) 机制工作:
- 当创建一个
shared_ptr指向对象时,引用计数初始化为1。 - 每当一个新的
shared_ptr拷贝或赋值指向同一对象时,引用计数加 1。 - 当
shared_ptr被销毁(如超出作用域)或指向其他对象时,引用计数减 1。 - 当引用计数变为
0时,自动调用对象的析构函数释放内存。
基本用法
1. 创建 shared_ptr
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "构造函数" << std::endl; }
~MyClass() { std::cout << "析构函数" << std::endl; }
void do_something() { std::cout << "执行操作" << std::endl; }
};
int main() {
// 方法 1:通过 std::make_shared 创建(推荐,更高效)
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
// 方法 2:通过 new 手动创建(不推荐,需手动传入指针)
std::shared_ptr<MyClass> ptr2(new MyClass());
return 0; // 离开作用域时,引用计数归 0,自动释放内存
}
2. 共享所有权
int main() {
auto ptr1 = std::make_shared<MyClass>(); // 引用计数 = 1
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数 = 2(拷贝赋值)
ptr1->do_something(); // 通过指针调用成员函数
(*ptr2).do_something(); // 解引用后调用
// ptr1 销毁(超出作用域),引用计数 = 1
// ptr2 销毁,引用计数 = 0 → 调用析构函数
return 0;
}
3. 获取原始指针
通过 get() 方法获取原始指针(谨慎使用,避免手动管理):
auto ptr = std::make_shared<MyClass>();
MyClass* raw_ptr = ptr.get(); // 获取原始指针
// 注意:不要用 delete raw_ptr,否则会导致 double free 错误
常见场景
-
多所有者共享对象
当一个对象需要被多个组件共享(如多线程访问同一资源),shared_ptr确保只有当所有使用者都不再需要时才释放内存。 -
容器存储动态对象
在容器中存储shared_ptr可以安全管理动态对象的生命周期:#include <vector> std::vector<std::shared_ptr<MyClass>> vec; vec.push_back(std::make_shared<MyClass>()); vec.push_back(std::make_shared<MyClass>()); // 容器销毁时,所有 shared_ptr 自动释放对象 -
避免悬垂指针
相比普通指针,shared_ptr不会出现“对象已释放但指针仍指向该地址”的悬垂指针问题。
注意事项
-
避免循环引用
两个shared_ptr互相引用会导致引用计数无法归 0,造成内存泄漏。解决方法是使用std::weak_ptr打破循环:struct A { std::shared_ptr<B> b; }; struct B { std::shared_ptr<A> a; }; // 循环引用示例(内存泄漏) auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b = b; b->a = a; // 互相引用,引用计数永远为 1 -
不要用同一个原始指针创建多个
shared_ptr
会导致重复释放内存(double free):MyClass* raw = new MyClass(); std::shared_ptr<MyClass> p1(raw); std::shared_ptr<MyClass> p2(raw); // 错误!p1 和 p2 会各自释放 raw -
优先使用
std::make_shared
相比shared_ptr(new T()),std::make_shared可以一次性分配对象和引用计数的内存,更高效且能避免某些异常安全问题。
总结
std::shared_ptr 是 C++ 中管理共享对象生命周期的核心工具,通过引用计数自动释放内存,大幅降低内存泄漏风险。适用于多个所有者需要共享同一对象的场景,但需注意避免循环引用等问题。
lambda表达式
你可能是指“lambda 表达式”(Lambda Expression),这是 C++11 及以上版本引入的匿名函数特性,允许在代码中直接定义短小的函数对象,无需单独声明函数。
核心作用
lambda 表达式的主要价值是简化代码,尤其适合作为“临时使用的小函数”(如回调函数、算法参数等),避免单独定义函数的繁琐。
基本语法
lambda 表达式的基本格式如下:
[capture](parameters) -> return_type {
// 函数体
}
各部分含义:
[capture]:捕获列表,用于捕获外部变量(lambda 表达式所在作用域的变量)到函数体内使用。(parameters):参数列表,与普通函数的参数列表用法相同(可省略,若没有参数)。-> return_type:返回类型(可选,若函数体只有一条return语句,编译器可自动推导)。{ ... }:函数体,包含具体逻辑。
常见用法示例
1. 最简单的 lambda(无参数、无返回值)
#include <iostream>
int main() {
// 定义并立即调用 lambda
[]() {
std::cout << "Hello, Lambda!" << std::endl;
}(); // 末尾的 () 表示立即执行
return 0;
}
2. 带参数和返回值的 lambda
#include <iostream>
int main() {
// 带参数和返回值的 lambda(返回类型自动推导)
auto add = [](int a, int b) {
return a + b;
};
std::cout << add(3, 5) << std::endl; // 输出 8
return 0;
}
3. 捕获外部变量(重点)
lambda 可以通过 [capture] 捕获外部变量,常见捕获方式:
[]:不捕获任何变量。[=]:按值捕获所有外部变量(函数体内为副本,修改不影响外部)。[&]:按引用捕获所有外部变量(函数体内修改会影响外部)。[x, &y]:按值捕获x,按引用捕获y。
示例:
#include <iostream>
int main() {
int x = 10, y = 20;
// 按值捕获 x,按引用捕获 y
auto func = [x, &y]() {
// x = 100; // 错误:按值捕获的变量不能修改
y = 200; // 正确:按引用捕获的变量可以修改
std::cout << "x = " << x << ", y = " << y << std::endl;
};
func(); // 输出 x = 10, y = 200
std::cout << "外部 y = " << y << std::endl; // 输出 200(被修改)
return 0;
}
4. 作为算法参数(典型场景)
在 STL 算法中,lambda 常用于简化回调逻辑(如排序、遍历):
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5};
// 用 lambda 作为排序规则(降序)
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 从大到小排序
});
// 用 lambda 遍历输出
for_each(nums.begin(), nums.end(), [](int num) {
std::cout << num << " "; // 输出 5 4 3 1 1
});
return 0;
}
与普通函数的区别
- 匿名性:lambda 表达式没有函数名,通常通过
auto赋值给变量使用。 - 捕获变量:可以直接捕获外部作用域的变量(普通函数需要通过参数传递)。
- 生命周期:通常在定义的作用域内使用,适合短期临时逻辑。
总结
lambda 表达式是 C++ 中简化代码的强大工具,尤其适合编写短小的回调函数或算法参数,避免单独定义函数的冗余。其核心是通过简洁的语法定义匿名函数,并支持灵活捕获外部变量,在现代 C++ 编程中应用广泛。
std::function
什么是std::function?
std::function 是 C++11 引入的标准库模板类(定义在 <functional> 头文件中),用于封装任何可调用对象(函数、lambda 表达式、函数对象、绑定表达式、成员函数指针等),形成一个类型安全的通用函数包装器。
它的核心价值是消除不同可调用对象之间的类型差异,让各种调用形式统一成相同的接口,便于存储、传递和使用。
基本用法
std::function 需要指定一个函数签名作为模板参数,格式为:
std::function<返回值类型(参数类型1, 参数类型2, ...)>
例如:
std::function<int(int, int)>:可封装“接收两个int参数、返回int”的可调用对象。std::function<void()>:可封装“无参数、无返回值”的可调用对象。
支持的可调用对象类型
std::function 几乎可以包装所有“可被调用”的实体:
-
普通函数
int add(int a, int b) { return a + b; } std::function<int(int, int)> func = add; -
lambda 表达式
std::function<int(int, int)> func = [](int a, int b) { return a * b; }; -
函数对象(仿函数)
struct Multiply { int operator()(int a, int b) { return a * b; } }; std::function<int(int, int)> func = Multiply(); -
类成员函数
class Calculator { public: int subtract(int a, int b) { return a - b; } }; Calculator calc; // 绑定成员函数时需指定对象 std::function<int(int, int)> func = std::bind(&Calculator::subtract, &calc, std::placeholders::_1, std::placeholders::_2); -
std::bind 绑定的表达式
using namespace std::placeholders; auto bound = std::bind(add, _2, _1); // 交换参数顺序 std::function<int(int, int)> func = bound;
典型应用场景
-
回调函数
作为函数参数传递,在特定事件(如按钮点击、异步操作完成)发生时执行。// 接收一个回调函数,在完成任务后调用 void do_task(std::function<void(int)> callback) { int result = 42; // 模拟任务结果 callback(result); // 执行回调 } // 使用时传入lambda作为回调 do_task([](int res) { std::cout << "任务完成,结果:" << res << std::endl; }); -
存储函数列表
把多个函数放入容器,实现批量执行或动态选择执行逻辑。std::vector<std::function<void()>> actions; actions.push_back([]() { std::cout << "步骤1" << std::endl; }); actions.push_back([]() { std::cout << "步骤2" << std::endl; }); // 依次执行所有函数 for (auto& action : actions) { action(); } -
实现策略模式
动态切换算法或行为,无需修改调用代码。// 定义策略接口 using SortStrategy = std::function<void(std::vector<int>&)>; // 具体策略 void bubble_sort(std::vector<int>& v) { /* 冒泡排序实现 */ } void quick_sort(std::vector<int>& v) { /* 快速排序实现 */ } // 排序器(依赖策略) class Sorter { private: SortStrategy strategy; public: Sorter(SortStrategy s) : strategy(s) {} void sort(std::vector<int>& v) { strategy(v); } }; // 使用时动态选择策略 Sorter s(quick_sort); // 用快速排序 std::vector<int> data = {3,1,4}; s.sort(data);
注意事项
-
空状态检查
未绑定任何可调用对象的std::function处于“空状态”,调用时会抛出std::bad_function_call异常。可通过operator bool()检查:std::function<void()> func; if (func) { // 检查是否绑定了函数 func(); } -
性能开销
std::function内部通过类型擦除实现,调用时会有轻微的性能损耗(通常可忽略,但不适合极致性能场景)。 -
与函数指针的区别
函数指针只能指向普通函数或静态成员函数,而std::function支持更广泛的可调用对象,且类型更灵活。
总结
std::function 是 C++ 中处理“函数作为数据”的强大工具,它统一了各种可调用对象的接口,使得函数可以像普通变量一样被存储、传递和使用,极大提升了代码的灵活性和可扩展性,尤其在回调机制、事件驱动编程等场景中不可或缺。
为什么需要用std::function包装无参无返回值的函数,而不是直接调用?
这本质上是关于“直接调用”与“间接调用”的区别,std::function的价值体现在需要“灵活传递、存储、延迟执行”函数的场景,即使是无参void函数也不例外。
核心原因:函数作为“数据”的需求
直接调用函数适用于“明确知道要调用哪个函数,且立即执行”的场景。但在很多情况下,我们需要把函数像普通变量一样:
- 存储起来(比如放进容器,后续再执行)
- 传递给其他函数(比如作为回调函数)
- 动态切换要执行的函数(比如根据条件选择不同实现)
这时std::function就成了理想的“函数容器”,即使是无参void函数也能从中受益。
具体场景举例
假设我们有几个无参void函数:
#include <functional>
#include <vector>
#include <iostream>
void print_hello() { std::cout << "Hello "; }
void print_world() { std::cout << "World "; }
void print_endl() { std::cout << std::endl; }
1. 批量存储并批量执行
如果想把这些函数放进一个列表,然后依次执行,直接调用做不到,但std::function可以:
// 用func_t存储多个无参void函数
using func_t = std::function<void()>;
std::vector<func_t> actions;
// 像存储数据一样存储函数
actions.push_back(print_hello);
actions.push_back(print_world);
actions.push_back(print_endl);
// 批量执行(遍历容器调用)
for (auto& f : actions) {
f(); // 依次执行三个函数,输出 "Hello World "
}
2. 作为回调函数传递
如果需要在某个事件发生后(比如按钮点击、定时结束)执行函数,std::function可以作为“回调参数”传递:
// 一个模拟“延迟执行”的函数,接收一个无参void函数作为回调
void delay_execute(func_t callback, int ms) {
// 模拟延迟(实际中可能是异步操作)
// ...
callback(); // 延迟后执行传入的函数
}
// 使用时:传递不同的无参函数作为回调
delay_execute(print_hello, 1000); // 1秒后执行print_hello
delay_execute(print_endl, 2000); // 2秒后执行print_endl
3. 封装匿名函数(lambda)
对于临时定义的lambda表达式(无参void类型),std::function是存储和传递它们的便捷方式:
func_t f = [](){
std::cout << "这是一个临时定义的无参函数" << std::endl;
};
f(); // 执行lambda
总结
直接调用函数是“即时执行已知函数”,而std::function的价值是让函数具备“可存储、可传递、可动态绑定”的特性,即使是最简单的无参void函数,在需要这些特性的场景下(如回调、批量执行、动态逻辑),std::function能极大提升代码的灵活性。
简单说:直接调用是“用函数做事”,std::function是“把函数当数据用”。
7950

被折叠的 条评论
为什么被折叠?



