C++新特性
6/17/25About 8 min
C++新特性
一、C++11新特性
1. 内存序(Memory Order)及原子类型(atomic)
(1)原子类型简介
原子类型是C++11引入的特殊类型,其操作保证不可分割(atomicity),不会被线程调度打断。原子操作在硬件层面由CPU指令直接支持,无需使用锁。使用时需引入头文件#include <atomic>
。
C++标准库提供的原子类型:
原子类型 | 对应内置类型 |
---|---|
std::atomic<bool> | bool |
std::atomic<char> | char |
std::atomic<int> | int |
std::atomic<long> | long |
std::atomic<long long> | long long |
std::atomic<void*> | void* |
(2)内存序简介
内存序是C++原子操作的一个参数,用于控制原子操作之间的内存可见性和指令重排序规则。
C++11定义了6种内存序:
内存序标签 | 说明 |
---|---|
memory_order_relaxed | 仅保证原子性,不保证内存可见性顺序(最弱) |
memory_order_consume | 对依赖此操作的读操作施加顺序约束 |
memory_order_acquire | 阻止后续读操作被重排序到此操作之前 |
memory_order_release | 阻止前面的写操作被重排序到此操作之后 |
memory_order_acq_rel | 同时具有acquire和release的语义 |
memory_order_seq_cst | 顺序一致性(默认),提供最强的内存可见性保证 |
(3)基本原子操作
所有原子类型都支持以下操作:
store()
:写入值(原子写)load()
:读取值(原子读)exchange()
:交换值并返回旧值compare_exchange_weak()
/compare_exchange_strong()
:比较并交换
(4)数值原子类型的特殊操作
数值原子类型(如atomic<int>
)额外支持:
fetch_add()
:原子加法(返回旧值)fetch_sub()
:原子减法(返回旧值)++
/--
:原子自增/自减
(5)内存序使用示例
- relaxed内存序
std::atomic<int> x(0);
std::atomic<int> y(0);
void thread1() {
x.store(1, std::memory_order_relaxed); // 原子写x
y.store(1, std::memory_order_relaxed); // 原子写y
}
void thread2() {
while (y.load(std::memory_order_relaxed) == 0) {
// 等待y变为1
}
// 此时x可能仍为0(由于指令重排序或缓存延迟)
std::cout << "x: " << x.load(std::memory_order_relaxed) << std::endl;
}
- release-acquire内存序
std::atomic<int> data(0);
std::atomic<bool> ready(false);
void producer() {
data.store(42, std::memory_order_relaxed); // 写数据
ready.store(true, std::memory_order_release); // 发布数据(release)
}
void consumer() {
while (!ready.load(std::memory_order_acquire)) { // 等待数据(acquire)
// 自旋等待
}
// 此时保证data已被写入
std::cout << "data: " << data.load(std::memory_order_relaxed) << std::endl;
}
(6)原子操作与内存序常用组合模式
- 原子标志(Atomic Flag)
std::atomic<bool> flag(false);
void worker() {
// 等待标志变为true
while (!flag.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
// 执行任务...
}
void controller() {
// 执行准备工作...
flag.store(true, std::memory_order_release); // 通知worker
}
- 双重检查锁定(Double-Checked Locking)
class Singleton {
private:
static std::atomic<Singleton*> instance;
static std::mutex mutex;
Singleton() = default;
~Singleton() = default;
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); // 获取屏障
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mutex);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
std::atomic_thread_fence(std::memory_order_release); // 释放屏障
instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
};
(7)总结与对比
原子操作与锁的性能对比
- 轻量级同步:原子操作(尤其是relaxed内存序)比锁快几个数量级
- 重量级同步:当竞争激烈时,原子操作可能比锁更慢(因缓存一致性开销)
使用建议
最小化原子操作:仅在必要时使用原子类型
选择合适的内存序:避免使用默认的
seq_cst
,根据需求选择更弱的内存序减少原子变量竞争:使用线程局部存储或无锁数据结构
原子类型适用场景
- 计数器、标志位等简单共享状态
- 无锁数据结构实现
- 线程间事件通知
内存序选择指建议
场景 推荐内存序 简单原子计数器 relaxed
发布-订阅模式 release
/acquire
顺序一致性要求 seq_cst
2. 自动类型推导 (auto)
(1)简介与作用
简介
C++11引入的auto
关键字用于自动类型推导,编译器会根据变量初始化表达式的类型自动推导出变量的类型,无需显式指定。作用:
- 简化复杂类型声明:如迭代器、模板类型等。
- 避免类型声明错误:确保类型一致性。
- 支持泛型编程:与模板、lambda表达式配合使用。
(2) 基础使用方法
// 基本类型声明与初始化
auto x = 10; // 自动推导为int
auto y = 3.14; // 自动推导为double
auto z = "hello"; // 自动推导为const char*
auto flag = true; // 自动推导为bool
// 复杂类型简化
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin(); // 自动推导为std::vector<int>::iterator
(3)与范围for循环结合
简化遍历数组、容器等可迭代对象的语法。
std::vector<int> vec = {1, 2, 3, 4};
// 只读遍历
for (auto val : vec) {
std::cout << val << " ";
}
// 可写遍历
for (auto& val : vec) {
val *= 2;
}
(4)引用与cv限定符
const int& ref = 10;
auto x = ref; // x类型为int(顶层const和引用被丢弃)
auto& y = ref; // y类型为const int&(保留引用和const)
const auto z = ref; // z类型为const int(保留const,丢弃引用)
(5)使用限制
- 必须初始化
auto x; // 错误:必须有初始化表达式
auto y = 10; // 正确
- 不能用于函数参数类型
void func(auto param) { // 错误:auto不能用于函数参数
// ...
}
- 不能用于数组类型推导
int arr[] = {1, 2, 3};
auto arr2 = arr; // arr2推导为int*,而非数组
- 注意类型推导陷阱
std::vector<bool> vec = {true, false};
auto val = vec[0]; // 推导为std::vector<bool>::reference,而非bool
// 可能导致悬空引用问题
3. Lambda 表达式
(1)简介
C++11引入的lambda函数是一种匿名函数对象,允许在代码中直接定义轻量级的可调用对象,无需显式定义函数或函数对象类。
(2) 核心语法
[capture](parameters) mutable(optional) exception(optional) -> return_type { body }
- 捕获子句(capture):定义如何捕获外部变量
- 参数列表(parameters):与普通函数参数类似
- mutable:允许修改捕获的变量(可选)
- 异常规范(exception):指定可能抛出的异常(可选)
- 返回类型(return_type):自动推导(可选)
- 函数体(body):实现具体功能
(3) 基础使用方法
- 无捕获、无参数的lambda
auto func = [] { return 42; };
std::cout << func() << std::endl; // 输出42
- 带参数的lambda
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4) << std::endl; // 输出7
- 显式指定返回类型
auto divide = [](double a, double b) -> double {
return a / b;
};
std::cout << divide(5.0, 2.0) << std::endl; // 输出2.5
(4) 捕获子句详解
- 值捕获(By Value)
int x = 10;
auto lambda = [x] { return x * 2; }; // 捕获x的值
std::cout << lambda() << std::endl; // 输出20
- 引用捕获(By Reference)
int y = 20;
auto lambda = [&y] { y *= 2; }; // 捕获y的引用
lambda();
std::cout << y << std::endl; // 输出40
- 混合捕获
int a = 1, b = 2, c = 3;
auto lambda = [a, &b, &c] {
b += a;
c += a;
};
lambda();
std::cout << b << ", " << c << std::endl; // 输出3, 4
- 隐式捕获
int total = 0;
int count = 5;
auto lambda = [&] { // 隐式引用捕获所有外部变量
total = (1 + count) * count / 2;
};
lambda();
std::cout << total << std::endl; // 输出15
(5) Lambda与STL算法结合
- 用于std::sort自定义比较
#include <algorithm>
#include <vector>
std::vector<int> vec = {3, 1, 4, 1, 5};
std::sort(vec.begin(), vec.end(),
[](int a, int b) { return a > b; }); // 降序排序
- 用于std::for_each遍历
std::vector<int> data = {1, 2, 3, 4};
int sum = 0;
std::for_each(data.begin(), data.end(),
[&sum](int val) { sum += val; }); // 计算总和
std::cout << "Sum: " << sum << std::endl; // 输出10
- 用于std::find_if查找
std::vector<int> numbers = {10, 25, 30, 45};
auto it = std::find_if(numbers.begin(), numbers.end(),
[](int n) { return n % 3 == 0; }); // 查找第一个3的倍数
if (it != numbers.end()) {
std::cout << "Found: " << *it << std::endl; // 输出30
}
(6) 高级特性
- mutable关键字
允许修改值捕获的变量副本。
int x = 10;
auto lambda = [x]() mutable {
x += 5; // 修改捕获的x副本
return x;
};
std::cout << lambda() << std::endl; // 输出15
std::cout << x << std::endl; // 输出10(原值未变)
- 泛型Lambda(C++14)
auto genericLambda = [](auto a, auto b) {
return a + b;
};
std::cout << genericLambda(1, 2) << std::endl; // 输出3(int+int)
std::cout << genericLambda(1.5, 2.5) << std::endl; // 输出4.0(double+double)
- 捕获表达式(C++14)
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)] {
return *p;
};
std::cout << lambda() << std::endl; // 输出42
(7)Lambda的实现原理
编译器转换
编译器会将lambda表达式转换为一个匿名函数对象类(闭包类),包含:- 重载的
operator()
方法 - 捕获的变量作为类成员
- 重载的
示例
int x = 10;
auto lambda = [x](int y) { return x + y; };
// 等价于以下类
class CompilerGeneratedClass {
private:
int x; // 捕获的变量
public:
CompilerGeneratedClass(int x) : x(x) {}
int operator()(int y) const { return x + y; }
};
// 调用等价于
CompilerGeneratedClass functor(x);
functor(5); // 返回15
4. 智能指针
自动管理内存,避免内存泄漏。
std::unique_ptr<int> ptr(new int(42)); // 独占所有权
std::shared_ptr<int> sptr = std::make_shared<int>(42); // 共享所有权
4. 右值引用和移动语义
减少不必要的对象复制,提高性能。
std::vector<int> source = {1, 2, 3};
std::vector<int> dest = std::move(source); // 移动而非复制
二、C++14 新特性
1. 泛型Lambda
Lambda 表达式的参数可以使用 auto。
auto genericLambda = [](auto x, auto y) { return x + y; };
2. 返回类型推导
函数可以使用 auto 自动推导返回类型。
auto add(int a, int b) { return a + b; }
3. 变量模板
模板可以实例化为变量。
template <typename T>
constexpr T pi = T(3.1415926535897932385);
三、C++17 新特性
1. 结构化绑定
分解结构体、元组或数组的元素。
std::pair<int, std::string> p = {42, "hello"};
auto [num, str] = p; // 同时定义 num 和 str
2. if constexpr
在编译时执行条件判断。
template <typename T>
auto get_value(T t) {
if constexpr (std::is_integral_v<T>) {
return t * 2;
} else {
return t;
}
}
3. 文件系统库 (std::filesystem)
跨平台文件操作。
#include <filesystem>
namespace fs = std::filesystem;
fs::path p = "example.txt";
if (fs::exists(p)) {
std::cout << "File size: " << fs::file_size(p) << " bytes\n";
}