C++11-17 新特性详解
基于作者10年工作经验整理的编程笔记,涵盖C++11/14/17核心语言特性与实战技巧。
简介
C++11 到 C++17 是 C++ 语言的一次重大升级,引入了 lambda 表达式、auto 类型推导、移动语义、智能指针、thread_local 存储类型等重要语言特性。本文聚焦于这些核心语言特性,结合实际开发经验,帮助开发者深入理解和正确使用现代 C++ 的关键能力。
一、Lambda 表达式
1.1 基本语法- [capture](parameters) -> return_type { body }
复制代码- // 基本用法
- auto add = [](int a, int b) -> int {
- return a + b;
- };
- cout << add(3, 5) << endl; // 输出 8
复制代码1.2 捕获列表- int x = 10, y = 20;
- // 值捕获
- auto f1 = [x, y]() { return x + y; };
- // 引用捕获
- auto f2 = [&x, &y]() { x++; y++; };
- // 全部值捕获
- auto f3 = [=]() { return x + y; };
- // 全部引用捕获
- auto f4 = [&]() { x++; y++; };
- // 混合捕获
- auto f5 = [&x, y]() { x += y; };
复制代码1.3 Lambda 与函数指针
没有捕获变量的 lambda 可以转换为函数指针: - typedef int(*FuncPtr)(int, int);
- FuncPtr ptr = [](int a, int b) -> int { return a + b; };
复制代码Lambda 表达式非常适合用作回调函数参数,简洁且耦合度低。
二、auto 与 decltype
2.1 auto 自动类型推导- auto a = 10; // int
- auto b = 3.14; // double
- auto c = "hello"; // const char*
- auto it = vec.begin(); // vector<int>::iterator
- // auto 只能在函数或复合语句中使用
复制代码2.2 decltype
decltype 获取表达式的类型: - int x = 10;
- decltype(x) y = 20; // y 的类型为 int
复制代码三、左值引用、右值引用与完美转发
3.1 基本概念
- int a = 10; // a 是左值,10 是右值
- int &lr = a; // 左值引用
- int &&rr = 10; // 右值引用
复制代码3.2 万能引用(转发引用)- template <typename T>
- void func(T&& arg); // 万能引用:可以接受左值和右值
复制代码3.3 std::move 与 std::forward- // std::move:将左值转换为右值引用(无运行时开销,只是类型转换)
- std::string str = "hello";
- std::string other = std::move(str); // str 变为空
- // std::forward:完美转发,保持参数的原始值类别
- template <typename T>
- void wrapper(T&& arg) {
- target(std::forward<T>(arg)); // 保持 arg 的左值/右值属性
- }
复制代码重点:std::move 和 std::forward 不会产生任何字节的可执行代码,它们只是类型转换。
3.4 移动语义的编译器优化- A getA() {
- A a;
- return a; // 理论上触发移动构造函数
- }
- A b = getA();
- // VS:触发移动构造函数
- // GCC:可能进行 RVO/NRVO 优化,直接在 b 的位置构造 a,跳过移动
复制代码四、智能指针实战
4.1 shared_ptr 的 reset 误区- shared_ptr<int> pInt = make_shared<int>(45);
- // reset 不是直接释放内存,而是减少引用计数
- // 当引用计数减为 0 时才真正释放
- pInt.reset(); // 引用计数从 1 变为 0,释放内存
- // 注意作用域
- {
- shared_ptr<int> pInt = make_shared<int>(45);
- } // pInt 在此处释放
- // *pInt = 8; // 错误!pInt 已经被释放
复制代码4.2 shared_ptr 循环引用- class TestA {
- public:
- void func1(shared_ptr<TestB> ptrB) { m_ptrB = ptrB; }
- private:
- shared_ptr<TestB> m_ptrB; // 改为 weak_ptr 解决
- };
- class TestB {
- public:
- void func1(shared_ptr<TestA> ptrA) { m_ptrA = ptrA; }
- private:
- shared_ptr<TestA> m_ptrA; // 改为 weak_ptr 解决
- };
- // 循环引用导致两者都无法释放
- // 解决方法:将其中一个改为 weak_ptr
复制代码4.3 智能指针与异常安全
如果一个函数中有频繁的错误判断和返回,每次返回前都需要释放资源,则可将资源放入智能指针,因为智能指针是异常安全的。
五、std::function 与 std::bind
5.1 std::function
通用的函数包装器,可以包装函数指针、lambda、函数对象等: - std::function<int(int, int)> func;
- func = [](int a, int b) { return a + b; };
- func = std::plus<int>(); // 函数对象
- // 注意:std::function 不能直接赋值 nullptr
- // std::function<int(int,int)> f = nullptr; // 某些编译器可能报错
- // std::function<int(int,int)> f; // 默认为空,调用会抛出异常
复制代码5.2 std::bind- int func(int a, int& b, int c);
- // bind 默认是值传递
- auto bound = std::bind(func, a, std::ref(b), c);
- // 使用 std::ref 进行引用传递
复制代码5.3 std::ref 与 std::move
std::ref 用于在需要引用传递的地方包装参数,std::move 用于将左值转换为右值引用。
六、emplace 与优先队列
6.1 emplace
C++11 STL 新增 emplace 成员函数,参数直接传递构造参数,减少对象复制: - vector<pair<string, int>> vec;
- vec.push_back(make_pair("hello", 1)); // 需要构造临时对象
- vec.emplace_back("hello", 1); // 直接在容器中原地构造
复制代码6.2 priority_queue
自动排序的队列,新增元素自动排序: - // 默认大顶堆
- priority_queue<int> pq;
- pq.push(3);
- pq.push(1);
- pq.push(5);
- // top() 返回 5
- // 自定义比较(小顶堆)
- auto cmp = [](int a, int b) { return a > b; };
- priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);
复制代码七、thread_local
thread_local 是 C++11 引入的存储类型,变量在每个线程中拥有独立的实例: - thread_local int count = 0;
- // 每个线程都有自己的 count 副本
- // 线程开始时构造,线程结束时析构
- // 可以与 static/extern 联合使用
- thread_local static int var; // 线程局部 + 静态存储
复制代码C++ 中四种存储周期:
- automatic:栈上,函数结束释放
- static:静态存储区,程序结束释放
- dynamic:堆上,手动释放
- thread:线程存储,线程结束释放
八、位运算技巧
8.1 基本运算
| 运算 | 用途 |
|---|
| n & 1 | 判断奇偶 | | a ^= b; b ^= a; a ^= b; | 不用临时变量交换两个数 | | ~n + 1 | 求相反数 | | x << n | 乘以 2^n | | x >> n | 除以 2^n | | n & (n-1) == 0 | 判断是否为 2 的幂 | | `n | = (1 << k)` | | n &= ~(1 << k) | 将第 k 位设为 0 | | x ^= (1 << n) | 切换第 n 位 | | (x >> n) & 1 | 获取第 n 位的值 |
8.2 统计二进制中 1 的个数(汉明重量)- int count = 0;
- while (n) {
- n &= (n - 1); // 清除最低位的 1
- count++;
- }
复制代码九、实用技巧
9.1 整型溢出- uint32_t vara = 12340;
- uint64_t varb = 1024 * 1024 * vara; // 溢出!字面值运算时已溢出
- uint64_t varb = 1024 * 1024 * (uint64_t)vara; // 正确
复制代码9.2 snprintf 返回值- char buf[32];
- int len = snprintf(buf, sizeof(buf), "hello %s", name);
- // len 是实际写入的字符数(不含 '\0')
- // 如果 len >= sizeof(buf),说明被截断
复制代码9.3 pragma 指令- #pragma once // 防止头文件重复包含
- #pragma pack(n) // 设置结构体对齐
- #pragma comment(lib, "mylib") // 指定链接库
- #pragma warning(push) // 保存警告状态
- #pragma warning(disable: 4996) // 禁用指定警告
- #pragma warning(pop) // 恢复警告状态
复制代码9.4 资源释放原则
遵循栈的出栈入栈规则:
- 被依赖的资源先初始化(入栈)
- 释放时先释放有依赖的模块(出栈)
- 否则依赖的资源先被释放,其他模块将无法正常运行
总结
C++11 到 C++17 引入的语言特性极大地提升了 C++ 的表达能力和安全性。从 lambda 表达式到移动语义,从 auto 类型推导到智能指针,这些特性让 C++ 代码更简洁、更高效、更安全。
关键实战要点:
- 熟练使用 lambda 表达式和 std::function,简化回调和函数对象
- 理解移动语义和完美转发,避免不必要的拷贝开销
- 使用智能指针和 RAII 模式管理资源,杜绝内存泄漏
- 掌握 emplace 等新接口,优化容器操作性能
- 了解 thread_local 等线程相关语言特性
原始笔记来源: hhjt/c++11learn.cpp |