如果你在 C++11 之前写过 C++,你熟悉的可能是这样的代码:手动 new/delete、原始指针满天飞、for(int i=0; i<v.size(); i++)、写模板时一屏编译错误、用 typedef 定义类型别名、boost 补标准库的缺……
当你再回来看现代 C++ 代码时,可能会觉得”这还是 C++ 吗?”
这篇文章帮你系统地跨越这道鸿沟。不是罗列所有新特性(那是几本书的量),而是聚焦在你日常开发中最常用、对写法影响最大的变化上。
版本时间线
先对齐一下年份和版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
| C++98/03 ── 你熟悉的版本
│
│ (漫长的 8 年空白)
│
C++11 ── 2011 ── 革命性版本!现代 C++ 的起点
C++14 ── 2014 ── C++11 的小修补
C++17 ── 2017 ── 实用特性大量加入
C++20 ── 2020 ── 又一次大升级(Concepts、Ranges、Coroutines)
C++23 ── 2023 ── 继续完善
C++26 ── 2026 ── 制定中
从 C++11 开始,每 3 年发布一个新版本
C++11 的重要程度 ≈ 重新发明了 C++
|
一、智能指针 —— 告别手动内存管理
1.1 以前:new/delete 的噩梦
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // C++98 风格:手动管理内存
void oldStyle() {
Widget* w = new Widget();
// ... 一堆代码 ...
if (error) {
delete w; // 别忘了释放!
return; // 每个退出路径都要 delete
}
// ... 更多代码 ...
doSomething(w); // w 还有效吗?谁拥有它?
delete w; // 双重 delete?忘记 delete?
}
|
问题一大堆:忘记 delete 导致内存泄漏、多次 delete 导致崩溃、异常抛出时来不及 delete、不清楚”谁负责释放”……
1.2 现在:智能指针自动管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #include <memory>
// 现代 C++ 风格
void modernStyle() {
// unique_ptr:独占所有权,离开作用域自动释放
auto w = std::make_unique<Widget>();
if (error) {
return; // 不需要手动释放!自动析构
}
doSomething(w.get()); // 传原始指针给不涉及所有权的函数
// 函数结束,w 自动释放,不可能泄漏
}
|
1.3 三种智能指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // ① unique_ptr —— 独占所有权(最常用,默认选它)
auto p1 = std::make_unique<Widget>();
// 不能复制,只能移动
// auto p2 = p1; // ❌ 编译错误
auto p2 = std::move(p1); // ✅ 所有权转移,p1 变空
// ② shared_ptr —— 共享所有权(引用计数)
auto p3 = std::make_shared<Widget>();
auto p4 = p3; // ✅ 引用计数 +1
// p3 和 p4 都销毁后,Widget 才被释放
// ③ weak_ptr —— 弱引用(不增加引用计数,解决循环引用)
std::weak_ptr<Widget> wp = p3;
if (auto sp = wp.lock()) { // 尝试获取 shared_ptr
sp->doSomething(); // 成功才能使用
}
|
1.4 现代 C++ 的内存管理原则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| 新原则(重要!):
1. 永远不要写 new 和 delete
→ 用 make_unique / make_shared 替代
2. 默认用 unique_ptr
→ 只在真正需要共享所有权时用 shared_ptr
3. 函数参数传递规则:
→ 不涉及所有权:传引用(const Widget&)或原始指针(Widget*)
→ 要转移所有权:传 unique_ptr(按值或 std::move)
→ 要共享所有权:传 shared_ptr
4. 原始指针仍然可以用
→ 但只用于"借用",不用于"拥有"
|
二、auto 和类型推导 —— 让编译器帮你写类型
2.1 以前:类型写到手酸
1
2
3
| // C++98:每个变量都要写完整类型
std::map<std::string, std::vector<int>>::iterator it = myMap.begin();
std::pair<std::string, int> result = getResult();
|
2.2 现在:auto 搞定
1
2
3
4
| // 现代 C++:编译器自动推导类型
auto it = myMap.begin(); // 编译器知道它是什么类型
auto result = getResult(); // 自动推导返回类型
auto lambda = [](int x) { return x * 2; }; // Lambda 的类型无法手写
|
2.3 auto 的使用指南
1
2
3
4
5
6
7
8
9
| // ✅ 适合用 auto 的场景
auto it = container.begin(); // 迭代器类型太长
auto ptr = std::make_unique<Widget>(); // 右侧已经明确类型
auto [key, value] = *it; // 结构化绑定(C++17)
auto result = someFunction(); // 函数返回类型复杂
// ❌ 不建议用 auto 的场景
auto x = 42; // int 直接写 int 就好,auto 反而不清晰
auto price = 9.99; // 是 float 还是 double?写明确更好
|
2.4 结构化绑定(C++17)—— 解包利器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 以前:访问 pair/tuple 的元素
std::pair<int, std::string> p = {1, "hello"};
int id = p.first;
std::string name = p.second;
// 现在:一行解包
auto [id, name] = p; // 直接拆开!
// 遍历 map 也变得优雅
std::map<std::string, int> scores = { {"Alice", 90}, {"Bob", 85} };
// 以前
for (auto it = scores.begin(); it != scores.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
// 现在
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
|
三、移动语义 —— 性能的免费午餐
3.1 以前的问题:不必要的复制
1
2
3
4
5
6
7
8
| // C++98:返回一个大对象
std::vector<int> createLargeVector() {
std::vector<int> v;
// ... 往 v 里塞 100 万个元素 ...
return v; // 返回时:复制 100 万个元素!然后销毁原来的 v
} // 白白浪费一次拷贝
std::vector<int> result = createLargeVector(); // 又一次拷贝
|
3.2 现在:移动而不是复制
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 移动语义的直觉:
// 拷贝 = 复印一份文件(耗时,占空间)
// 移动 = 把文件从一个文件夹拖到另一个文件夹(几乎免费)
std::vector<int> a = {1, 2, 3, 4, 5};
// 拷贝:a 的所有元素被复制到 b,a 不变
std::vector<int> b = a; // 拷贝构造
// 移动:a 的内部指针直接交给 c,a 变为空
std::vector<int> c = std::move(a); // 移动构造,几乎零开销
// 此时 a 为空,c 拥有了原来 a 的数据
|
3.3 移动背后发生了什么
1
2
3
4
5
6
7
8
9
10
11
12
| vector<int> 内部结构(简化):
拷贝前:
a: [指针 → 堆上的数据: 1,2,3,4,5] size=5 capacity=5
拷贝后(深拷贝,O(n)):
a: [指针 → 堆上的数据: 1,2,3,4,5] size=5 capacity=5
b: [指针 → 新的堆数据: 1,2,3,4,5] size=5 capacity=5 ← 分配新内存 + 复制
移动后(偷指针,O(1)):
a: [指针 → nullptr] size=0 capacity=0 ← 被掏空了
c: [指针 → 堆上的数据: 1,2,3,4,5] size=5 capacity=5 ← 直接拿走指针
|
3.4 右值引用(&&)
1
2
3
4
5
6
7
8
9
10
11
| // 左值 vs 右值
int x = 42; // x 是左值(有名字,有地址)
// 42 是右值(临时值,没名字)
// 右值引用:可以"绑定"到临时值
void process(std::string&& s) {
// s 是一个右值引用,意味着"这个字符串即将被销毁,你可以偷它的数据"
myString = std::move(s); // 零拷贝接管
}
process("hello " + "world"); // 临时字符串直接移动进去,无需拷贝
|
3.5 你自己的类怎么支持移动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| class MyBuffer {
int* data;
size_t size;
public:
// 拷贝构造(以前就有)
MyBuffer(const MyBuffer& other)
: data(new int[other.size]), size(other.size) {
std::copy(other.data, other.data + size, data); // 真正复制数据
}
// 移动构造(C++11 新增)
MyBuffer(MyBuffer&& other) noexcept
: data(other.data), size(other.size) { // 偷指针
other.data = nullptr; // 把原对象置空
other.size = 0;
}
// 移动赋值运算符
MyBuffer& operator=(MyBuffer&& other) noexcept {
if (this != &other) {
delete[] data; // 释放自己的资源
data = other.data; // 偷对方的
size = other.size;
other.data = nullptr; // 把对方置空
other.size = 0;
}
return *this;
}
~MyBuffer() { delete[] data; }
};
|
3.6 实际影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 现代 C++ 中,以下操作都已经很高效(不再有额外拷贝):
// 返回大对象 —— 编译器 RVO + 移动语义
std::vector<int> createVector() {
std::vector<int> v(1000000);
return v; // 零拷贝(RVO 直接构造到调用方,连移动都省了)
}
// 往容器里插入临时对象
std::vector<std::string> v;
v.push_back("hello"); // 临时 string 被移动进容器
v.emplace_back("hello"); // 更好:直接在容器内部构造,连移动都省了
// std::move 在容器操作中
std::string s = "very long string...";
v.push_back(std::move(s)); // s 被移动进容器,不拷贝
// s 现在为空
|
四、Lambda 表达式 —— 函数也能当变量传
4.1 以前:函数指针和 functor 的痛苦
1
2
3
4
5
6
7
8
9
10
11
12
13
| // C++98:要传一个"自定义行为"给算法,选择很少
// 方式 1:函数指针
bool compare(int a, int b) { return a > b; }
std::sort(v.begin(), v.end(), compare);
// 方式 2:函数对象(functor),写一个完整的类
struct Compare {
bool operator()(int a, int b) const { return a > b; }
};
std::sort(v.begin(), v.end(), Compare());
// 要写的东西太多了,就为了一个简单的"从大到小排序"
|
4.2 现在:Lambda 一行搞定
1
2
3
| // 现代 C++:Lambda 表达式
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// ↑ 就地定义一个匿名函数
|
4.3 Lambda 的完整语法
1
2
3
4
5
6
7
8
9
10
11
12
| // Lambda 的组成部分:
// [捕获列表](参数列表) -> 返回类型 { 函数体 }
auto greet = [](const std::string& name) -> std::string {
return "Hello, " + name;
};
std::cout << greet("World"); // "Hello, World"
// 返回类型通常可以省略(自动推导)
auto greet = [](const std::string& name) {
return "Hello, " + name;
};
|
4.4 捕获列表:Lambda 的核心特色
Lambda 可以”捕获”它外面的变量,这是函数指针做不到的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| int threshold = 10;
std::string prefix = "Item: ";
// 按值捕获 [=]:复制一份外部变量
auto f1 = [=](int x) { return x > threshold; };
// 按引用捕获 [&]:直接引用外部变量
auto f2 = [&](int x) {
threshold = x; // 可以修改外部变量
};
// 指定捕获方式
auto f3 = [threshold, &prefix](int x) {
// threshold 是副本(不能修改原始值)
// prefix 是引用(可以修改原始值)
prefix += std::to_string(x);
return x > threshold;
};
// C++14:初始化捕获(可以移动对象进 Lambda)
auto ptr = std::make_unique<Widget>();
auto f4 = [p = std::move(ptr)]() {
p->doSomething(); // unique_ptr 被移动进来了
};
|
4.5 Lambda 的实际应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // ① STL 算法配合 Lambda(最常见)
auto it = std::find_if(users.begin(), users.end(),
[](const User& u) { return u.age > 18; });
// ② 回调函数
button.onClick([&]() {
statusBar.setText("Button clicked!");
});
// ③ 自定义排序
std::sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.gpa > b.gpa; // 按 GPA 从高到低
});
// ④ 立即执行(IIFE,用于复杂初始化)
const auto config = [&]() {
Config c;
c.loadFromFile("config.json");
c.validate();
return c;
}(); // 注意末尾的 (),表示立即调用
|
五、范围 for 循环 和 Range-based 操作
5.1 循环的进化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // C++98:下标循环
for (int i = 0; i < vec.size(); i++) {
std::cout << vec[i] << std::endl;
}
// C++98:迭代器循环
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << std::endl;
}
// C++11:范围 for 循环
for (const auto& item : vec) {
std::cout << item << std::endl;
}
// 简洁、安全、不会越界
|
5.2 Ranges(C++20)—— 链式操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| #include <ranges>
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 以前:过滤偶数 → 乘以 2 → 取前 3 个
std::vector<int> result;
for (int n : numbers) {
if (n % 2 == 0) {
result.push_back(n * 2);
if (result.size() == 3) break;
}
}
// result = {4, 8, 12}
// C++20 Ranges:管道风格,像流水线一样
auto result = numbers
| std::views::filter([](int n) { return n % 2 == 0; }) // 过滤偶数
| std::views::transform([](int n) { return n * 2; }) // 每个乘 2
| std::views::take(3); // 取前 3 个
// result 是一个惰性视图:{4, 8, 12}
// 关键:中间没有创建任何临时容器!
|
六、字符串和容器的改进
6.1 字符串字面量和 string_view
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // C++98
const char* s1 = "hello"; // C 字符串
std::string s2 = "hello"; // 需要堆分配
// C++14 自定义字面量
using namespace std::string_literals;
auto s3 = "hello"s; // 直接是 std::string 类型
auto s4 = "hello"sv; // std::string_view(C++17)
// string_view:只读的字符串引用,零拷贝
void printName(std::string_view name) { // 不管传入什么类型都不拷贝
std::cout << name << std::endl;
}
printName("hello"); // ✅ C 字符串
printName(std::string("hi")); // ✅ std::string
printName(someStringView); // ✅ string_view
|
6.2 新增容器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // C++11 新增的容器
// 无序容器(哈希表实现,O(1) 查找)
std::unordered_map<std::string, int> hashMap; // 以前只有 std::map(红黑树,O(log n))
std::unordered_set<int> hashSet;
// 固定大小数组
std::array<int, 5> arr = {1, 2, 3, 4, 5}; // 替代 C 数组 int arr[5]
// 支持 .size()、.begin()、越界检查等
// 元组
auto t = std::make_tuple(1, "hello", 3.14);
auto [i, s, d] = t; // C++17 结构化绑定
// C++17 新增
std::optional<int> maybeValue; // 可能有值,也可能没有(替代"用 -1 表示无效")
std::variant<int, std::string> v; // 类型安全的 union
std::any a; // 可以存任何类型
|
6.3 optional —— 告别”魔术值”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // 以前:用特殊值表示"没有结果"
int findIndex(const std::vector<int>& v, int target) {
for (int i = 0; i < v.size(); i++) {
if (v[i] == target) return i;
}
return -1; // -1 表示没找到,但谁规定 -1 就是无效的?
}
// 现在:用 optional 明确表达"可能没有"
std::optional<int> findIndex(const std::vector<int>& v, int target) {
for (int i = 0; i < v.size(); i++) {
if (v[i] == target) return i;
}
return std::nullopt; // 明确表示"没有值"
}
// 使用
if (auto idx = findIndex(v, 42)) { // optional 可以隐式转 bool
std::cout << "Found at " << *idx << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
|
七、constexpr —— 编译期计算
7.1 以前:运行时算 vs 宏
1
2
3
4
5
| // C++98:编译期常量只能用宏或 enum
#define MAX_SIZE 1024
enum { BUFFER_SIZE = 256 };
// 宏的问题:没有类型检查、不受作用域限制、调试困难
|
7.2 现在:constexpr 让函数在编译时运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // C++11/14/17:constexpr 函数
constexpr int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 编译期就算好了!不占运行时间
constexpr int result = factorial(10); // 编译期结果 = 3628800
static_assert(factorial(5) == 120); // 编译期断言
// constexpr if(C++17)—— 编译期条件分支
template<typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2; // 整数走这个分支
} else {
return value + 0.5; // 浮点数走这个分支
}
// 不满足条件的分支在编译期就被丢弃,不会产生代码
}
|
八、并发编程 —— 标准库终于有了线程支持
8.1 以前:平台相关的 API
1
2
3
4
| // C++98:没有标准线程库
// Windows: CreateThread / WaitForSingleObject
// Linux: pthread_create / pthread_join
// 写一份跨平台多线程代码非常痛苦
|
8.2 现在:标准线程库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #include <thread>
#include <mutex>
#include <future>
// 创建线程
std::thread t([]() {
std::cout << "Hello from thread!" << std::endl;
});
t.join(); // 等待线程结束
// 互斥锁(用 lock_guard 自动管理)
std::mutex mtx;
void safeIncrement(int& counter) {
std::lock_guard<std::mutex> lock(mtx); // 构造时加锁,析构时自动解锁
counter++;
// 不需要手动 unlock,异常安全
}
// C++17:scoped_lock(可同时锁多个互斥量,防止死锁)
std::scoped_lock lock(mutex1, mutex2);
// async + future:异步执行并获取结果
auto future = std::async(std::launch::async, []() {
// 在另一个线程中执行
return computeExpensiveResult();
});
// 继续做其他事情...
auto result = future.get(); // 需要结果时再等待
|
8.3 原子操作
1
2
3
4
5
6
7
8
9
| #include <atomic>
// 以前:手动加锁保护一个简单的计数器
// 现在:原子变量,无锁线程安全
std::atomic<int> counter{0};
// 多个线程可以安全地操作
counter++; // 原子操作,无需加锁
counter.fetch_add(5); // 原子加 5
|
九、模板和泛型编程的进化
9.1 以前的模板:编译错误天书
1
2
3
4
| // C++98 模板错误信息的噩梦:
// 你写了 std::sort(myList.begin(), myList.end())
// 编译器吐出 200 行错误,核心意思是 list 不支持随机访问迭代器
// 但你需要从天书般的错误信息中自己找出来
|
9.2 Concepts(C++20)—— 模板约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // C++20:用 Concepts 约束模板参数
template<typename T>
concept Sortable = requires(T a) {
{ a.begin() } -> std::random_access_iterator;
{ a.size() } -> std::convertible_to<size_t>;
};
template<Sortable Container>
void mySort(Container& c) {
std::sort(c.begin(), c.end());
}
mySort(myVector); // ✅ vector 支持随机访问
mySort(myList); // ❌ 编译错误:清晰地告诉你 list 不满足 Sortable 约束
|
9.3 变参模板(C++11)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 以前:要写多个重载来处理不同数量的参数
void print(int a) { std::cout << a; }
void print(int a, int b) { std::cout << a << b; }
void print(int a, int b, int c) { std::cout << a << b << c; }
// ... 无穷无尽
// 现在:变参模板,任意数量、任意类型
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // C++17 折叠表达式
}
print(1); // 1
print(1, " hello ", 3.14); // 1 hello 3.14
|
9.4 类型特征(Type Traits)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| #include <type_traits>
// 编译期检查类型属性
static_assert(std::is_integral_v<int>); // int 是整数类型
static_assert(std::is_floating_point_v<double>); // double 是浮点类型
static_assert(std::is_same_v<decltype(42), int>); // 42 的类型是 int
// 条件编译
template<typename T>
void process(T value) {
if constexpr (std::is_pointer_v<T>) {
std::cout << "pointer: " << *value << std::endl;
} else {
std::cout << "value: " << value << std::endl;
}
}
|
十、错误处理的进化
10.1 以前:错误码 + 异常混用
1
2
3
4
5
| // C++98:错误处理方式不统一
int result = doSomething();
if (result == -1) { /* 错误 */ } // 错误码
if (result == NULL) { /* 错误 */ } // 空指针
// 还有 errno、throw、assert... 风格不统一
|
10.2 现在:更丰富的工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // ① optional:表示"可能没有值"
std::optional<User> findUser(int id);
// ② noexcept:承诺不抛异常
void fastOperation() noexcept; // 编译器可以据此优化
// 移动构造函数应该标 noexcept
// ③ [[nodiscard]]:强制调用方处理返回值
[[nodiscard]] ErrorCode init();
init(); // ⚠️ 编译器警告:你忽略了返回值!
// ④ std::expected(C++23):返回值或错误
std::expected<User, ErrorCode> findUser(int id) {
if (id <= 0) return std::unexpected(ErrorCode::InvalidId);
return User{id, "Alice"};
}
auto result = findUser(42);
if (result) {
std::cout << result->name;
} else {
handleError(result.error());
}
|
十一、构建工具和项目管理
11.1 CMake 成为事实标准
1
2
3
4
5
| 你那个时代 现在
手写 Makefile → CMake(事实标准)
IDE 管理构建 → CMakeLists.txt + 任意 IDE
./configure && make → cmake -B build && cmake --build build
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 现代 CMake(CMakeLists.txt)
cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20) # 使用 C++20
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(myapp
src/main.cpp
src/widget.cpp
)
# 链接第三方库
find_package(fmt REQUIRED)
target_link_libraries(myapp PRIVATE fmt::fmt)
|
11.2 包管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| 以前:手动下载源码、编译、配置路径
或者把第三方库源码直接放进项目
现在:
├── vcpkg → 微软出品,最流行
│ vcpkg install fmt nlohmann-json spdlog
│
├── Conan → 分布式包管理器
│ conan install . --build=missing
│
└── CMake FetchContent → 直接在 CMakeLists.txt 里声明依赖
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt
GIT_TAG 10.2.1
)
FetchContent_MakeAvailable(fmt)
|
11.3 必备的现代 C++ 第三方库
1
2
3
4
5
6
7
8
9
| 库名 用途 替代了什么
fmt 格式化输出 printf / iostream
spdlog 日志 手写日志或 log4cxx
nlohmann/json JSON 解析 手动拼字符串
Catch2 / GTest 单元测试 手写测试
{fmt} 已进入 C++20 std::format
doctest 轻量级测试框架
abseil Google 的 C++ 基础库
|
十二、编码风格的变迁
12.1 新旧风格对照表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| // ═══════ 变量初始化 ═══════
// 以前
int x = 42;
Widget w(arg1, arg2);
int arr[] = {1, 2, 3};
// 现在(统一初始化 / 花括号初始化)
int x{42};
Widget w{arg1, arg2};
std::vector<int> v{1, 2, 3};
// 花括号初始化可以防止窄化转换
int x{3.14}; // ❌ 编译错误,double 到 int 会丢失精度
// ═══════ 空指针 ═══════
// 以前
int* p = NULL;
if (p == 0) { ... }
// 现在
int* p = nullptr; // 类型安全的空指针
if (p == nullptr) { ... }
// ═══════ 类型别名 ═══════
// 以前
typedef std::vector<std::pair<std::string, int>> UserList;
// 现在(更清晰,支持模板)
using UserList = std::vector<std::pair<std::string, int>>;
// using 还能做模板别名(typedef 做不到)
template<typename T>
using Vec = std::vector<T>;
Vec<int> numbers;
// ═══════ 枚举 ═══════
// 以前(枚举值泄漏到外层作用域)
enum Color { RED, GREEN, BLUE };
int x = RED; // RED 污染了全局命名空间
// 现在(强类型枚举)
enum class Color { Red, Green, Blue };
Color c = Color::Red; // 不会污染命名空间
// int x = Color::Red; // ❌ 不能隐式转换为 int
// ═══════ 函数返回多值 ═══════
// 以前(输出参数)
bool getSize(int& width, int& height);
// 现在(返回结构体或 tuple)
struct Size { int width; int height; };
Size getSize();
auto [w, h] = getSize(); // C++17 结构化绑定
|
十三、学习路径建议
13.1 分阶段学习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| 第一阶段:最核心的改变(1-2 周)
├── auto 和类型推导
├── 范围 for 循环
├── nullptr(替代 NULL)
├── 智能指针(unique_ptr / shared_ptr)
├── Lambda 表达式
└── 花括号初始化 / enum class / using
→ 学完这些,你的代码就已经"现代化"了
第二阶段:性能和设计(2-3 周)
├── 移动语义和右值引用
├── string_view
├── optional / variant
├── constexpr
└── 标准线程库
→ 学完这些,你能写出高效的现代代码
第三阶段:高级特性(按需)
├── 变参模板 + 折叠表达式
├── Concepts(C++20)
├── Ranges(C++20)
├── Coroutines(C++20)
├── Modules(C++20,编译速度革命)
└── std::format / std::expected(C++23)
→ 按实际需要挑选学习
|
13.2 推荐资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 入门书籍:
《Effective Modern C++》—— Scott Meyers
最经典的现代 C++ 指南,42 条实践建议
覆盖 C++11/14,是你的第一本书
《A Tour of C++》—— Bjarne Stroustrup(C++ 之父)
快速概览整个现代 C++,薄且精
进阶:
《C++ Templates: The Complete Guide》第 2 版
模板元编程的圣经
在线资源:
cppreference.com —— 最权威的 C++ 参考手册
godbolt.org —— 在线编译器,看汇编输出
cppinsights.io —— 看编译器如何展开你的代码
isocpp.org —— C++ 标准委员会官网
|
13.3 从今天开始的最小改动清单
如果你有一个老项目,以下是投入最小、收益最大的改动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| 优先级 1(立即改):
├── new/delete → make_unique / make_shared
├── NULL → nullptr
├── typedef → using
└── 传统 for → 范围 for
优先级 2(重构时改):
├── 返回值 → 考虑移动语义
├── 回调函数 → Lambda
├── enum → enum class
└── 老式 cast → static_cast / dynamic_cast
优先级 3(新代码默认用):
├── auto(迭代器、工厂函数返回值)
├── constexpr(编译期常量)
├── optional(可能无值的返回)
├── string_view(只读字符串参数)
└── [[nodiscard]](不该忽略的返回值)
|
总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| C++98 现代 C++(C++11 → C++23)
new / delete → 智能指针(make_unique)
手写类型 → auto 推导
for(i=0;i<n;i++) → for(auto& x : container)
函数指针 / functor → Lambda
手动内存拷贝 → 移动语义(std::move)
NULL → nullptr
typedef → using
enum → enum class
平台相关线程 API → std::thread / async
手写 Makefile → CMake + vcpkg
Boost 补缺 → 标准库已吸收大部分 Boost 功能
模板错误天书 → Concepts(C++20)
|
一句话总结:现代 C++ 的核心哲学是——把资源管理交给编译器(RAII + 智能指针),把类型推导交给编译器(auto),把性能优化交给编译器(移动语义 + constexpr),你只需要清晰地表达意图。
从 C++11 到 C++23 的每一个版本,都在朝着”写更少的代码、犯更少的错、跑得更快”这个方向前进。你不需要一次学完所有特性——先掌握智能指针、auto、Lambda、移动语义这四样,你的代码就已经焕然一新了。
本文聚焦在最常用、影响最大的特性上,无法覆盖现代 C++ 的全部内容。如需深入某个特性,推荐 cppreference.com 作为参考手册,以及 Scott Meyers 的《Effective Modern C++》作为最佳实践指南。