跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++11 核心新特性实战:Lambda、移动语义与模板

C++11 引入 lambda 表达式简化匿名函数定义,通过移动语义优化资源管理,利用可变参数模板实现泛型编程,并结合 function 和 bind 包装器统一可调用对象接口。文章详细讲解了 Lambda 的语法与捕捉机制、移动构造与 delete/default 关键字的应用、参数包展开技巧以及包装器的实际使用场景。

小熊软糖发布于 2026/3/24更新于 2026/10/1085 浏览
C++11 核心新特性实战:Lambda、移动语义与模板

C++11 核心新特性实战:Lambda、移动语义与模板

1. Lambda 表达式

1.1 引入

Lambda 表达式本质上是一种匿名函数对象,允许直接在代码中定义小型函数,省去了额外定义函数或函数对象类的麻烦。理论描述往往枯燥,不如直接看场景。

以前我们想对自定义结构体排序,得写个仿函数类:

struct Goods {
    string _name;   // 名字
    double _price;  // 价格
    int _evaluate;  // 评价
    Goods(const char* str, double price, int evaluate) : _name(str), _price(price), _evaluate(evaluate) {}
};

struct ComparePriceLess {
    bool operator()(const Goods& gl, const Goods& gr) {
        return gl._price < gr._price;
    }
};

int main() {
    vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};
    sort(v.begin(), v.end(), ComparePriceLess());
    sort(v.begin(), v.end(), ComparePriceGreater());
}

商品排序需要用到算法库的 sort,设置自定义比较方式时,仿函数确实是个好选择。但随着 C++ 语法演进,这种写法显得过于繁琐。每次为了一个算法逻辑都要重新写个类,如果比较逻辑不同还得实现多个类,命名也容易冲突。于是 C++11 引入了 Lambda 表达式。

1.2 语法

Lambda 表达式书写格式:

[capture-list] (parameters) mutable -> return-type { statement }

各部分说明如下:

  • [capture-list]:捕捉列表,总是出现在 Lambda 函数开始位置。编译器根据 [] 判断后续代码是否为 Lambda 函数,它能捕捉上下文中的变量供 Lambda 使用。
  • (parameters):参数列表,与普通函数一致。若不需要参数,可连同 () 一起省略。
  • mutable:默认情况下 Lambda 函数是 const 的,mutable 可取消其常量性。使用该修饰符时,参数列表不可省略(即使为空)。
  • ->returntype:返回值类型,用追踪返回类型形式声明。无返回值时可省略。若返回值类型明确,也可由编译器推导省略。
  • {statement}:函数体,可使用参数及所有捕获到的变量。

注意: 参数列表和返回值类型都是可选忽略的,而捕捉列表和函数体可以为空。因此 C++11 中最简单的 Lambda 函数为 []{},虽然它不能做任何事情。

1.3 使用

有了 Lambda,上面的排序代码可以简化成这样:

int main() {
    vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) -> bool {
        return g1._price < g2._price;
    });
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) -> bool {
        return g1._price > g2._price;
    });
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) -> bool {
        return g1._evaluate < g2._evaluate;
    });
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) -> bool {
        return g1._evaluate > g2._evaluate;
    });
    return 0;
}

相比仿函数,Lambda 的可读性和便捷性高了很多。它实际上是一个匿名函数,无法直接调用,但可以用 auto 赋值给变量:

auto ret = [](const Goods& g1, const Goods& g2) {
    return g1._evaluate < g2._evaluate;
};

关于捕捉列表 [],平时用得不多,但某些场景必须掌握。它描述了上下文中哪些数据可以被 Lambda 使用,以及传值还是传引用:

  • [x]:值传递捕捉变量 x
  • [=]:值传递捕捉所有父作用域中的变量(包括 this)
  • [&x]:引用传递捕捉变量 x
  • [&]:引用传递捕捉所有父作用域中的变量(包括 this)
  • [this]:值传递捕捉当前的 this 指针

值得注意的点:

  1. 父作用域指包含 Lambda 函数的语句块。
  2. Lambda 默认以值传递方式工作,传值捕捉的变量在 Lambda 内部是不可修改的。
int main() {
    int x = 10;
    auto func = [x]() mutable {
        x = 20;
        cout << x << endl;
    };
    func();
    cout << x << endl;
    return 0;
}

加上 mutable 关键字就可以修改了,但这只是修改 Lambda 内部的副本,不会影响原始变量。再次输出 x 时,其值仍为 10。

  1. 语法上捕捉列表可由多个项组成,逗号分割。例如 [=, &a, &b] 表示引用传递 a 和 b,其他变量值传递;[&, a, this] 表示值传递 a 和 this,引用其他变量。
  2. 捕捉列表不允许变量重复传递,否则编译报错。
  3. 块作用域以外的 Lambda 函数捕捉列表必须为空,全局作用域中没有局部变量可供捕获。
  4. 块作用域中的 Lambda 仅能捕捉父作用域中局部变量,捕捉非此作用域或非局部变量会报错。
  5. Lambda 表达式之间不能相互赋值,即使类型相同,因为每个 Lambda 都有独特的未命名类型。

1.4 本质

Lambda 的本质是被包装的仿函数。转到反汇编可以看到,编译器会自动生成一个类,在该类中重载了 operator()。

2. 类的新增语法

2.1 移动构造、移动赋值运算符

C++11 新增了移动构造函数和移动赋值运算符重载。如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造。对于内置类型成员执行逐字节拷贝,自定义类型成员则看是否实现了移动构造,没实现就调用拷贝构造。

同理,如果你没有实现移动赋值重载,且满足上述条件,编译器也会自动生成默认移动赋值。如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

2.2 default

class Person {
public:
    Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
    Person(const Person& p) : _name(p._name), _age(p._age) {}
    Person(Person&& p) = default;
private:
    std::string _name;
    int _age;
};

int main() {
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    return 0;
}

default 是强制生成默认函数的关键字。我们提供了拷贝构造后,编译器就不会再生成移动构造了,此时可以使用 default 显式指定移动构造生成。

2.3 delete

class Person {
public:
    Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
    Person(const Person& p) = delete;
private:
    std::string _name;
    int _age;
};

int main() {
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    return 0;
}

delete 是禁止生成默认函数的关键字。当类显式删除了拷贝构造函数时,编译器不会自动生成移动构造函数(即使没有显式删除移动构造函数)。代码中没有显式定义移动构造函数,且隐式移动构造函数被禁用,因此无法完成移动初始化。

注意: 移动构造函数的核心目的是高效转移资源所有权(如动态内存、文件句柄等),而拷贝构造函数是创建独立副本。如果一个类禁用了拷贝构造函数,通常意味着资源不可复制或防止意外拷贝。此时若编译器仍自动生成移动构造函数,可能会破坏设计意图。

3. 可变参数模板

3.1 概念

其实可变模板参数早在 C 语言就有了,比如 printf("%d,%d,%d", x, y, z),后面的参数个数可以自己控制,这就是可变参数。回到 C++ 模板定义:

template<class... Args>
void ShowList(Args... args) {}

int main() {
    ShowList();
    ShowList(1);
    ShowList(1, 2.2);
    ShowList(1, 2, "xxxxx");
    return 0;
}

Args 是模板参数包,args 是函数形参参数包。声明 Args... args 可以包含 0 到任意个模板参数。

3.2 获取个数

template<class... Args>
void ShowList(Args... args) {
    cout << sizeof...(args) << endl;
}

int main() {
    ShowList();
    ShowList(1);
    ShowList(1, 2.2);
    ShowList(1, 2, "xxxxx");
    return 0;
}

这个用法虽然有点奇葩,但在调试或元编程时很有用。

3.3 展开参数包

想要用 for 循环遍历展开是不可行的,编译器不支持。这里主要有两种展开方法。

3.3.1 递归函数
// 递归终止函数
template<class T>
void ShowList(const T& t) {
    cout << t << endl;
}

// 展开函数
template<class T, class... Args>
void ShowList(T value, Args... args) {
    cout << value << " ";
    ShowList(args...);
}

int main() {
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', string("sort"));
    return 0;
}

模式匹配: 展开函数 ShowList(T value, Args... args) 匹配至少一个参数的情况,每次取出第一个参数 value,剩余参数构成新的参数包 args...。终止函数 ShowList(const T& t) 匹配仅有一个参数的情况,结束递归。

参数包展开: args... 在递归调用时会被解包,每次减少一个参数,直到参数包为空。关键语句 ShowList(args...) 会触发模板的递归实例化,直到匹配终止函数。

输出顺序: 先打印当前参数 value,再递归处理剩余参数,确保参数按传入顺序输出。

3.3.2 逗号表达式
template<class T>
void PrintArg(T t) {
    cout << t << " ";
}

// 展开函数
template<class... Args>
void ShowList(Args... args) {
    int arr[] = {(PrintArg(args), 0)...};
    cout << endl;
}

int main() {
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', string("sort"));
    return 0;
}

(PrintArg(args), 0)... 会将参数包 args... 展开为多个表达式,打印对应的值,然后返回 0 用于填充数组。

// 原始代码
int arr[] = {(PrintArg(args), 0)...};
// 展开后等价于
int arr[] = {(PrintArg(1), 0), (PrintArg('A'), 0), (PrintArg("sort"), 0)};

PrintArg 的返回值是 void,无法初始化 int 数组。即使 PrintArg 返回参数类型,参数包可能包含不同类型(如 int, char),仍会导致类型不匹配。每个元素必须是 int 类型,因此需要用 0 作为统一的返回值,保证初始化的数组元素都为相同类型。

3.4 emplace 系列的接口

int main() {
    list<pair<int, char>> mylist;
    mylist.emplace_back(10, 'a');
    mylist.emplace_back(20, 'b');
    mylist.emplace_back(make_pair(30, 'c'));
    mylist.push_back(make_pair(40, 'd'));
    mylist.push_back({50, 'e'});
    for (auto e : mylist)
        cout << e.first << ":" << e.second << endl;
    return 0;
}

emplace_back 的作用和 push_back 相同,但 mylist.emplace_back(20, 'b') 这种写法更方便一些。其实差别不大,emplace_back 是直接构造,push_back 是先构造再移动构造,移动构造消耗很小,实际影响有限。

3.5 可变参数模板的实际应用

class Date {
public:
    Date(int year = 1, int month = 1, int day = 1) : _year(year), _month(month), _day(day) {
        cout << "Date 构造" << endl;
    }
    Date(const Date& d) : _year(d._year), _month(d._month), _day(d._day) {
        cout << "Date 拷贝构造" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

template<class... Args>
Date* Create(Args... args) {
    Date* ret = new Date(args...);
    return ret;
}

int main() {
    list<Date> lt;
    Date d(2023, 9, 27); // 只能传日期类对象
    lt.push_back(d);
    // 既能传日期类对象,又能传日期类对象的参数包
    // 参数包一路往下传,直接去构造或者拷贝构造节点中日期类对象
    lt.emplace_back(d);
    lt.emplace_back(2023, 9, 27);
    return 0;
}

push_back 只能传日期类对象,emplace_back 既能传日期类对象,又能传日期类对象的参数包。参数包一路往下传,直接去构造或者拷贝构造节点中日期类对象。

4. 包装器

4.1 function

template<class F, class T>
T useF(F f, T x) {
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}

double f(double i) {
    return i / 2;
}

struct Functor {
    double operator()(double d) {
        return d / 3;
    }
};

int main() {
    // 函数名
    cout << useF(f, 11.11) << endl;
    // 函数对象
    cout << useF(Functor(), 11.11) << endl;
    // Lambda 表达式
    cout << useF([](double d) -> double { return d / 4; }, 11.11) << endl;
    return 0;
}

我们知道函数指针、仿函数、Lambda 表达式都是函数对象的创建方式。同时调用这三个方式实例化模板,useF 函数模板会实例化三份,明明内容相同,没必要导致模板效率低下。这时候就需要 <functional> 头文件中的 function 包装器。

function 也叫作适配器,本质是一个类模板。

// 类模板原型如下
template<class T> function; // undefined
template<class Ret, class... Args>
class function<Ret(Args...)>;

模板参数说明:

  • Ret:被调用函数的返回类型
  • Args...:被调用函数的形参

下面修改代码展示 function 的使用效果:

template<class F, class T>
T useF(F f, T x) {
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}

double f(double i) {
    return i / 2;
}

struct Functor {
    double operator()(double d) {
        return d / 3;
    }
};

int main() {
    // 函数名
    function<double(double)> func1 = f;
    cout << useF(func1, 11.11) << endl;
    // 函数对象
    function<double(double)> func2 = Functor();
    cout << useF(func2, 11.11) << endl;
    // Lambda 表达式
    function<double(double)> func3 = [](double d) -> double { return d / 4; };
    cout << useF(func3, 11.11) << endl;
    return 0;
}

三种可调用对象被统一为同一类型:包装类。模板只实例化一次,静态变量共享(即这个 count 只有一份)。

4.2 bind

bind 函数定义在头文件中,是一个函数模板,就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来'适应'原对象的参数列表。

bind 可以理解为一个接收包装类的适配器。上面的例子都是直接将函数给到包装类,而 bind 是将特定的函数和参数绑定到包装类。通过例子解析会更容易理解:

int Plus(int a, int b) {
    return a + b;
}

class Sub {
public:
    int sub(int a, int b) {
        return a - b;
    }
};

int main() {
    // 表示绑定函数 plus 参数分别由调用 func1 的第一、二个参数指定
    std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
    // auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
    // func2 的类型为 function<void(int, int, int)> 与 func1 类型一样
    // 表示绑定函数 plus 的第一、二为:1,2
    auto func2 = std::bind(Plus, 1, 2);
    cout << func1(1, 2) << endl;
    cout << func2() << endl;

    Sub s;
    // 绑定成员函数
    std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);
    // 参数调换顺序
    std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, placeholders::_2, placeholders::_1);
    cout << func3(1, 2) << endl;
    cout << func4(1, 2) << endl;
    return 0;
}

bind 的第一个参数传的是函数,后面的是一系列要传的参数,_1 为第一个参数,_2 为第二个参数,以此类推。参数既可以是待定的,也可以是具体的值。placeholders 属于 std 命名空间,若展开了就不用写。

注意:

  • 若函数是非静态成员函数,必须在 Sub::sub 前加上 &,因为非静态成员函数依赖对象,必须显式调用其地址。普通函数指针直接指向代码地址,而成员函数指针需要同时包含类的类型信息和函数地址,因此还需要将对象 s 传过去。
  • 若函数是静态成员函数,和普通函数一样都是全局函数,就不需要加 & 和传对象。

目录

  1. C++11 核心新特性实战:Lambda、移动语义与模板
  2. 1. Lambda 表达式
  3. 1.1 引入
  4. 1.2 语法
  5. 1.3 使用
  6. 1.4 本质
  7. 2. 类的新增语法
  8. 2.1 移动构造、移动赋值运算符
  9. 2.2 default
  10. 2.3 delete
  11. 3. 可变参数模板
  12. 3.1 概念
  13. 3.2 获取个数
  14. 3.3 展开参数包
  15. 3.3.1 递归函数
  16. 3.3.2 逗号表达式
  17. 3.4 emplace 系列的接口
  18. 3.5 可变参数模板的实际应用
  19. 4. 包装器
  20. 4.1 function
  21. 4.2 bind

更多推荐文章

查看全部
  • 仿生新势力:Openclaw 开源仿生爪如何革新机器人抓取
  • 飞算 JavaAI 专业版在 Java 微服务重构中的效率提升实践
  • DeepSeek 中冷启动数据与多阶段训练的作用解析
  • Spring AI 如何助力 Java 企业级 AI 应用开发
  • 如何使用 Kiro 进行 Java 开发
  • 超越 CRUD:用 JeecgBoot 低代码模式一天搭建请假审批系统
  • Python Streamlit:快速构建交互式数据 Web 应用
  • OpenClaw 多端交互实测指南:Web/TUI/钉钉集成配置
  • Llama-Factory 是否支持 FlashAttention 加速
  • Science Robotics 新突破:注意力机制结合强化学习实现足式机器人障碍穿越
  • 微信小程序接入 AI 服务实战:基于 Bmob SDK 实现智能对话
  • Python 核心数据结构详解:列表、元组、集合与字典
  • 大模型时代人形机器人感知:视觉 - 语言模型应用
  • Java HashMap 底层原理、源码设计与面试考点解析
  • PaperRed:AI 辅助论文写作与查重降重工具解析
  • 鸿蒙 Flutter 智能家居应用开发实战指南
  • Python 打造 AI 助手:文档总结、代码生成与资料检索
  • Dify 私有化部署教程:搭建专属 AI 知识库平台
  • PX4+ROS 无人机 Offboard 控制:模式解析与实战
  • Java 环境搭建与首个 Hello World 实战指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online