可变参数包 和 lambda表达式
在 C++ 中,参数包(Parameter Pack) 分为两种类型:
1. 类型参数包(Type Parameter Pack)
作用:表示一个类型的序列,用于模板编程中的类型推导和生成。
语法:在模板参数列表中使用 typename...
或 class...
。
典型用途:泛型编程、可变参数模板。
示例
cpp
template <typename... Args> // Args 是类型参数包 class Tuple {// 可存储任意数量和类型的元素 };// 使用 Tuple<int, double, std::string> t; // Args = {int, double, std::string}
2. 形参参数包(Function Parameter Pack)
作用:表示一个值的序列,用于函数参数列表。
语法:在函数参数列表中使用 Args... args
(需依赖类型参数包)。
典型用途:可变参数函数。
示例
cpp
template <typename... Args> void print(Args... args) { // args 是形参参数包// }// 使用 print(1, 3.14, "hello"); // args = {1, 3.14, "hello"}
关键操作
-
递归处理:通过模板递归展开形参参数包(C++11/14)。
cpp
template <typename T> void print(T t) {std::cout << t << "\n"; }template <typename T, typename... Args> void print(T t, Args... args) {std::cout << t << ", ";print(args...); // 递归展开 }
二者关系
-
类型参数包(
typename... Args
)定义了一组类型。 -
形参参数包(
Args... args
)是基于类型参数包生成的一组值。 -
配合使用:形参参数包必须依赖类型参数包。
完整示例
cpp
template <typename... Types> // 类型参数包 void bar(Types... values) { // values 是形参参数包std::tuple<Types...> data(values...); // 同时展开类型和值 }bar(42, "hello"); // Types = {int, const char*}, values = {42, "hello"}
应用场景
-
可变参数模板、可变参数函数
-
如
std::tuple
、std::variant
的底层实现。
-
-
完美转发
cpp
template <typename... Args> void wrapper(Args&&... args) {target(std::forward<Args>(args)...); // 保持值类别 }
Lambda 表达式的底层本质上是一个编译器生成的匿名仿函数类对象,下面详细解释它的工作原理和底层实现:
1. Lambda 表达式的基本结构
一个简单的 Lambda 表达式:
cpp
auto lambda = [](int x, int y) { return x + y; };
编译器会将其转换为类似以下的匿名仿函数类:
编译器生成的等效代码
cpp
// 编译器生成的匿名仿函数类 class __AnonymousLambdaClass { public:// 重载 operator(),自己不写返回类型底层就用auto推导auto operator()(int x, int y) const {return x + y;} };// Lambda 实例化 __AnonymousLambdaClass lambda; // 相当于 auto lambda = [](int x, int y) { ... };
2. Lambda 的底层实现细节
(1) Lambda 捕获列表 → 仿函数类的成员变量
如果 Lambda 捕获了外部变量,这些变量会成为该匿名类的成员变量:
cpp
int a = 10, b = 20; auto lambda = [a, &b](int x) { return a + x + b; };
编译器生成的类:
cpp
class __AnonymousLambdaClass { private:int a; // 值捕获 → 拷贝存储int& b; // 引用捕获 → 存储引用 public:// 构造函数初始化捕获的变量__AnonymousLambdaClass(int a_, int& b_) : a(a_), b(b_) {}auto operator()(int x) const {return a + x + b;} };// 实例化 Lambda __AnonymousLambdaClass lambda(a, b);
捕获方式的影响
捕获方式 | 存储方式 | 是否可修改外部变量 |
---|---|---|
[x] | 值拷贝 (const ) | ❌ 不能修改 |
[&x] | 引用 (int& ) | ✅ 可以修改 |
[=] | 所有变量值拷贝 | ❌ 不能修改 |
[&] | 所有变量引用 | ✅ 可以修改 |
[this] | 捕获当前类 this | 可访问类成员 |
(2) mutable
关键字 → 移除 operator()
的 const
默认情况下,Lambda 的 operator()
是 const
的,即不能修改值捕获的变量。如果加上 mutable
,则允许修改:
cpp
int x = 10; auto lambda = [x]() mutable { x++; };
编译器生成的类:
cpp
class __AnonymousLambdaClass { private:int x; // 值捕获,但允许修改 public:__AnonymousLambdaClass(int x_) : x(x_) {}// 注意:没有 const 修饰!auto operator()() {x++; // 允许修改} };
(3) 返回类型推断
如果 Lambda 的返回类型可以自动推导(如 return x + y;
),编译器会自动推断。也可以手动指定:
cpp
auto lambda = [](int x, int y) -> int { return x + y; };
生成的 operator()
返回类型:
cpp
auto operator()(int x, int y) const -> int {return x + y; }
3. Lambda 的底层优化
(1) 无捕获的 Lambda → 可转换为函数指针
如果 Lambda 不捕获任何变量,lambda底层为函数:
cpp
auto lambda = [](int x) { return x * 2; }; int (*func_ptr)(int) = lambda;
底层实现:
cpp
// 编译器生成一个静态函数(实际名称可能不同) static int __LambdaHelper_1234(int x) {return x * 2; }// Lambda 对象的行为(实际是函数指针) auto lambda = &__LambdaHelper_1234;// 因此可以赋值给函数指针 int (*func_ptr)(int) = lambda;