是从1.0版本移植过来的,但经过了gemini老师的润色和补充。

Modern C++ Overview: 从底层逻辑到语法糖

前言:上机课偶遇 Modern C++,拼劲全力无法战胜?在习惯了 Java 的自动内存管理和 Python 的灵活后,C++ 的语法规则确实显得复杂(甚至有些诡异)。

关于 C++ 的刻板印象 (Stereotypes):

  • Old, out-dated, less-frequently used
  • Unsafe (最大的痛点,内存泄露和越界)
  • Hard to use & Various Compilation Issues

尽管如此,我们仍然需要学习 Modern C++,因为它在系统编程和高性能领域依然是无可替代的。


1. Value Types & Move Semantics (值类型与移动语义)

左值 (lvalue) vs 右值 (rvalue)

  • 左值 (lvalue):表示占据内存中某个可识别位置(也就是一个地址)的对象。可以使用 & 取地址。
  • 右值 (rvalue):与之相反,通常是临时对象,不可以使用 & 取地址。

拷贝赋值 vs 移动赋值

对于赋值操作 a = xxx

  1. 拷贝赋值 (Copy Assignment):当 xxx 为左值时调用。
  2. 移动赋值 (Move Assignment):当 xxx 为右值时调用。

std::move

一个常识是:移动 (Move) 比拷贝 (Copy) 快。如果我们想移动一个左值呢?
我们可以使用 std::move。它让编译器认为某个左值是一个右值,从而实现所有权的转移

注意:使用了 std::move 后的对象不可再使用;此外,const 变量不可使用移动语义。

❌ 不可使用 std::move 的常见错误场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Print(const std::string &s);
std::string Concat(const std::string &p, std::string q) {
// 1. Move from a const var (错误:const 无法被移动,仍会触发拷贝)
std::string tmp = std::move(p);

// 2. Move from an rvalue (冗余:p+q 本身就是右值)
std::string tmp2 = std::move(p + q);

// 3. Move to a const reference (无意义:Print 接收的是引用,move 会产生临时变量)
Print(std::move(p + q));

// 4. Move the return value (错误:这会阻碍编译器进行返回值优化 RVO)
return std::move(ret);
}

2. Type Inference & std::forward (类型推导与完美转发)

Universal Reference (万能引用)

在模板中,T&& 并不总是代表右值引用,它可能是万能引用。它既可以接收左值,也可以接收右值。

1
2
3
4
5
6
7
8
template<typename T>
void func(T &&param) {
if constexpr (std::is_lvalue_reference_v<T>) {
std::cout << "传递的是左值" << std::endl;
} else {
std::cout << "传递的是右值" << std::endl;
}
}

3. Smart Pointers (智能指针)

Modern C++ 通过智能指针管理内存,遵循 RAII 原则,彻底告别裸指针。

std::unique_ptr

核心属性:独占所有权,不可拷贝(Copy Forbidden),只能移动(Move Only)。

用法:使用 std::make_unique(…) 创建。

std::shared_ptr

核心属性:多个所有者共享同一个资源。

引用计数:通过 control block 记录当前有多少个指针指向该对象,计数归零时自动释放。

std::weak_ptr (解决循环引用)

如果 A 指向 B,B 也指向 A(都是 shared_ptr),就会形成死锁。weak_ptr 不增加引用计数,可以观测对象但不会延长其生命周期。

1
2
3
4
struct Node {
std::shared_ptr<Node> next; // 若两个 Node 互相 next 指向,则内存泄漏
// 解决方法:将其中一个改为 std::weak_ptr<Node> next;
};

4. Syntax Sugar (语法糖)

auto 推断

auto 会自动推导变量类型,让代码更简洁,特别是处理迭代器或复杂模板时。

1
2
3
4
std::vector<int> vec = {1, 2, 3};
for (auto& elem : vec) { // auto& 推断为引用,可修改原容器
elem *= 2;
}

结构化绑定 (Structured Bindings - C++17)

一次性从 std::pair 或 std::tuple 中提取多个变量。

1
2
3
4
std::map<std::string, int> scores = {{"Alice", 90}};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}

5. Safety (C++17 安全容器)

为了避免使用不安全的 union 或容易导致错误的空指针。

std::any

可以存储任何类型的值,但在访问时必须通过 any_cast 进行类型安全检查。

std::optional

表示一个可能为空的值。不再需要通过返回 -1 或 nullptr 来表达“无效”。

1
2
3
4
std::optional<int> getValue(bool success) {
if (success) return 42;
return std::nullopt;
}

std::variant

类型安全的 union。它知道自己当前存的是哪种类型,并能通过 std::visit 优雅地处理。

1
2
3
using MyType = std::variant<int, std::string>;
MyType v = "Modern C++";
std::visit([](auto&& arg) { std::cout << arg; }, v);