C++11 :可变参数模板、Lambda 与包装器完全指南

前言

C++11 引入了可变参数模板,让模板编程更加灵活;Lambda 表达式让匿名函数的使用变得简单;而 std::functionstd::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 defaultdelete

class Person {
public:
    Person(Person&& p) = default;        // 强制生成默认移动构造
    Person(const Person& p) = delete;    // 禁止拷贝构造
};

3.3 finaloverride

  • 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 绑定参数,生成新可调用对象

使用建议

  1. 容器插入:优先使用 emplace_back / emplace
  2. 临时回调:优先使用 Lambda,而不是仿函数
  3. 类型擦除:使用 std::function 存储可调用对象
  4. 参数绑定:使用 std::bind 定制函数行为

更多推荐