C++11 :可变参数模板、Lambda 与包装器
·
C++11 :可变参数模板、Lambda 与包装器完全指南
前言
C++11 引入了可变参数模板,让模板编程更加灵活;Lambda 表达式让匿名函数的使用变得简单;而 std::function 和 std::bind 则提供了强大的可调用对象包装能力。本文将系统讲解这些特性。
一、可变参数模板
1.1 基本语法
可变参数模板允许接受任意数量、任意类型的参数:
template <class ...Args>
void Func(Args... args) {} // 参数包
class ...Args:模板参数包,表示零或多个类型Args... args:函数参数包,表示零或多个函数参数
1.2 sizeof... 计算参数个数
template <class ...Args>
void Print(Args&&... args) {
cout << sizeof...(args) << endl; // 输出参数个数
}
int main() {
Print(); // 0
Print(1); // 1
Print(1, string("xxx")); // 2
Print(1.1, "xxx", 2); // 3
return 0;
}
1.3 原理:编译器实例化
编译器会根据调用情况,实例化出多个重载函数:
// 对于 Print(1, string("xxx"), 2.2);
// 编译器会生成类似:
void Print(int&& arg1, string&& arg2, double&& arg3);
1.4 递归展开参数包
// 终止函数
void ShowList() {
cout << endl;
}
// 递归展开
template <class T, class ...Args>
void ShowList(T x, Args... args) {
cout << x << " ";
ShowList(args...); // 递归调用,每次减少一个参数
}
template <class ...Args>
void Print(Args... args) {
ShowList(args...);
}
int main() {
Print(1, string("hello"), 2.2);
// 输出:1 hello 2.2
return 0;
}
1.5 包扩展(Pattern Expansion)
使用 ... 触发包扩展,可以对每个元素应用模式:
template <class T>
const T& GetArg(const T& x) {
cout << x << " ";
return x;
}
template <class ...Args>
void Print(Args... args) {
Arguments(GetArg(args)...); // 展开为 GetArg(arg1), GetArg(arg2), ...
}
二、emplace 系列接口
2.1 基本用法
C++11 为 STL 容器新增了 emplace / emplace_back / emplace_front 接口:
template <class... Args>
void emplace_back(Args&&... args);
2.2 与 push_back 的对比
| 方式 | 步骤 | 效率 |
|---|---|---|
push_back |
先构造临时对象,再拷贝/移动到容器 | 可能多一次构造 |
emplace_back |
直接在容器空间上构造对象 | 更高效 |
2.3 示例
list<pair<string, int>> lt;
// push_back:先构造 pair,再拷贝到节点
pair<string, int> kv("苹果", 1);
lt.push_back(kv);
// emplace_back:直接传递构造参数
lt.emplace_back("苹果", 1); // 直接在节点空间构造 pair
2.4 模拟实现
template<class T>
struct ListNode {
T _data;
// 万能构造:接受任意参数,直接构造 _data
template <class... Args>
ListNode(Args&&... args)
: _data(std::forward<Args>(args)...)
{}
};
template<class T>
class list {
public:
template <class... Args>
void emplace_back(Args&&... args) {
insert(end(), std::forward<Args>(args)...);
}
template <class... Args>
iterator insert(iterator pos, Args&&... args) {
Node* newnode = new Node(std::forward<Args>(args)...);
// 插入节点...
}
};
✅ 推荐:优先使用
emplace系列替代push/insert系列。
三、新的类功能
3.1 默认的移动构造和移动赋值
C++11 新增了两个默认成员函数:
| 条件(都没有手动实现) | 编译器行为 |
|---|---|
| 无析构、无拷贝构造、无拷贝赋值 | 自动生成移动构造 / 移动赋值 |
规则:
- 内置类型:逐成员按字节拷贝
- 自定义类型:调用其移动构造/赋值,没有则调用拷贝版本
class Person {
public:
Person(const char* name = "", int age = 0)
: _name(name), _age(age)
{}
// 没有实现析构、拷贝构造、拷贝赋值
// 编译器会自动生成移动构造和移动赋值
private:
bit::string _name;
int _age;
};
3.2 default 和 delete
class Person {
public:
Person(Person&& p) = default; // 强制生成默认移动构造
Person(const Person& p) = delete; // 禁止拷贝构造
};
3.3 final 和 override
final:禁止类被继承或虚函数被重写override:显式表示重写基类虚函数
四、Lambda 表达式
4.1 基本语法
[capture-list](parameters) -> return_type { function_body }
各部分说明:
| 部分 | 说明 | 是否可省略 |
|---|---|---|
[capture-list] |
捕捉列表 | 不可省略 |
(parameters) |
参数列表 | 可省略 |
-> return_type |
返回值类型 | 可省略(自动推导) |
{ function_body } |
函数体 | 不可省略 |
4.2 示例
int main() {
// 最简单的 lambda
auto func1 = [] { cout << "hello" << endl; };
func1();
// 带参数和返回值
auto add = [](int x, int y) -> int { return x + y; };
cout << add(1, 2) << endl;
// 自动推导返回值
auto mul = [](int x, int y) { return x * y; };
// 交换两个数
int a = 1, b = 2;
auto swap1 = [](int& x, int& y) {
int tmp = x;
x = y;
y = tmp;
};
swap1(a, b);
return 0;
}
4.3 捕捉列表
| 捕捉方式 | 语法 | 说明 |
|---|---|---|
| 值捕捉 | [x, y] |
拷贝,不可修改 |
| 引用捕捉 | [&x, &y] |
引用,可修改 |
| 隐式值捕捉 | [=] |
自动值捕捉用到的变量 |
| 隐式引用捕捉 | [&] |
自动引用捕捉用到的变量 |
| 混合捕捉 | [=, &x] |
除 x 外都值捕捉 |
| 混合捕捉 | [&, x] |
除 x 外都引用捕捉 |
int a = 1, b = 2, c = 3;
// 值捕捉:不能修改
auto f1 = [a, b] { return a + b; };
// 引用捕捉:可以修改
auto f2 = [&a, &b] { a++; b++; };
// 隐式值捕捉
auto f3 = [=] { return a + b + c; };
// 隐式引用捕捉
auto f4 = [&] { a++; b++; c++; };
// 混合:其他值捕捉,a 引用捕捉
auto f5 = [=, &a] { a++; return b + c; };
4.4 mutable 关键字
值捕捉的变量默认是 const 的,使用 mutable 可以修改(但不影响外部):
int a = 10;
auto f = [a]() mutable {
a++; // 修改的是拷贝,不影响外部
return a;
};
cout << f() << endl; // 11
cout << a << endl; // 10
4.5 应用场景
struct Goods {
string _name;
double _price;
int _evaluate;
};
int main() {
vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3.0, 4}};
// 按价格升序
sort(v.begin(), v.end(),
[](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
// 按评价降序
sort(v.begin(), v.end(),
[](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate;
});
return 0;
}
4.6 原理:底层是仿函数
// 你写的 lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
// 编译器生成类似:
class lambda_1 {
public:
lambda_1(double rate) : _rate(rate) {}
double operator()(double money, int year) const {
return money * _rate * year;
}
private:
double _rate;
};
五、包装器
5.1 std::function
std::function 是一个可调用对象的包装器,可以存储函数指针、仿函数、Lambda 等。
#include <functional>
int add(int a, int b) { return a + b; }
struct Mul {
int operator()(int a, int b) { return a * b; }
};
int main() {
function<int(int, int)> f1 = add; // 函数指针
function<int(int, int)> f2 = Mul(); // 仿函数
function<int(int, int)> f3 = [](int a, int b) { return a - b; }; // Lambda
cout << f1(1, 2) << endl; // 3
cout << f2(2, 3) << endl; // 6
cout << f3(5, 2) << endl; // 3
return 0;
}
5.2 实战:逆波兰表达式求值
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
map<string, function<int(int, int)>> opFuncMap = {
{"+", [](int x, int y) { return x + y; }},
{"-", [](int x, int y) { return x - y; }},
{"*", [](int x, int y) { return x * y; }},
{"/", [](int x, int y) { return x / y; }}
};
for (auto& str : tokens) {
if (opFuncMap.count(str)) {
int right = st.top(); st.pop();
int left = st.top(); st.pop();
st.push(opFuncMap[str](left, right));
} else {
st.push(stoi(str));
}
}
return st.top();
}
};
5.3 std::bind
std::bind 用于绑定参数,生成新的可调用对象。
#include <functional>
using namespace placeholders; // _1, _2, _3...
int Sub(int a, int b) { return (a - b) * 10; }
int main() {
// 正常调用
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl; // 50
// 调整参数顺序
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl; // -50
// 绑死第一个参数
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl; // 950
// 绑死第二个参数
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl; // -950
// 绑定成员函数
Plus pd;
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
}
5.4 实际应用:定制化利率计算
// 计算复利利息
auto func1 = [](double rate, double money, int year) -> double {
double ret = money;
for (int i = 0; i < year; i++) {
ret += ret * rate;
}
return ret - money;
};
// 绑死利率和年限,生成不同产品
function<double(double)> product_3y_1p5 = bind(func1, 0.015, _1, 3);
function<double(double)> product_5y_1p5 = bind(func1, 0.015, _1, 5);
function<double(double)> product_10y_2p5 = bind(func1, 0.025, _1, 10);
cout << product_3y_1p5(1000000) << endl; // 3年1.5%复利利息
cout << product_5y_1p5(1000000) << endl; // 5年1.5%复利利息
cout << product_10y_2p5(1000000) << endl; // 10年2.5%复利利息
六、总结
| 特性 | 核心作用 |
|---|---|
| 可变参数模板 | 接受任意数量/类型的参数 |
emplace 系列 |
直接在容器空间构造对象,更高效 |
| 移动构造/赋值(默认) | 编译器自动生成,提升效率 |
default / delete |
精确控制默认函数生成 |
| Lambda 表达式 | 定义匿名函数对象,简洁灵活 |
std::function |
统一包装各种可调用对象 |
std::bind |
绑定参数,生成新可调用对象 |
使用建议
- 容器插入:优先使用
emplace_back/emplace - 临时回调:优先使用 Lambda,而不是仿函数
- 类型擦除:使用
std::function存储可调用对象 - 参数绑定:使用
std::bind定制函数行为
更多推荐



所有评论(0)