C++新特性梳理01

之前一直在准备暑期实习的笔试和面试, 因此博客断更了很久…回头做6.5840发现自己的代码逻辑都快忘了…

先整理下实习求职过程中学习的C++的新特性, 由于C++11已经使用得很广泛了, 包括智能指针、各种转换运算符、lambda表达式已经成为面试常见考点了,这里我就不提这些了,因此我只梳理从C++17开始的知识点,包括C++17、C++20、C++23和少量C++26。

之前断更的6.5840的最后一个lab4B也会补上

1 auto作为函数形参

C++20 中允许在函数声明中直接使用 auto 作为参数类型,这其实是一种简化模板函数定义的语法糖。该 auto 的使用方式类似于模板函数,但语法更为简洁。

  • 原始的函数模板实现
    1
    2
    3
    4
    template<typename T>
    void f(T x) {
    cout << x << endl;
    }
  • auto作为形参的实现
    1
    2
    3
    void f(auto x) {
    cout << x << endl;
    }

原理

当编译器遇到一个使用 auto 作为参数类型的函数定义时,它会将这个函数视为一个模板函数。编译器内部的处理过程如下:

  1. 模板化:编译器将 auto 参数的函数自动转换为模板函数。上述 void f(auto x) 实际上被编译器转换为:

    1
    2
    3
    4
    template<typename T>
    void f(T x) {
    cout << x << endl;
    }

    这里的 T 是由编译器自动生成的一个模板参数。

  2. 类型推导:当这个函数被调用时,编译器将根据传入的实参推导出 T 的具体类型。

2 <=>运算符

C++20 引入的了新的比较运算符<=>, 该运算符允许在一个表达式中执行多态的、全面的比较,并根据比较结果返回一个可传递到std::strong_orderingstd::weak_orderingstd::partial_ordering枚举类型的值, 其比较逻辑如下:。

1
a <=> b

比较左右两边的操作数ab,并可能返回以下三个值之一:

  1. std::strong_ordering:

    • std::strong_ordering::less:如果a严格小于b
    • std::strong_ordering::equal:如果a等于b
    • std::strong_ordering::greater:如果a严格大于b
  2. std::weak_ordering(适用于无法提供强排序的对象,比如浮点数NaN):

    • std::weak_ordering::less
    • std::weak_ordering::equivalent(不同于等于,可能涉及NaN的比较)
    • std::weak_ordering::greater
  3. std::partial_ordering(用于那些只在部分情况下可以比较的对象):

    • std::partial_ordering::less
    • std::partial_ordering::equivalent
    • std::partial_ordering::greater
    • std::partial_ordering::unordered(表示两个操作数不可比较)

简而言之, <=>使得单一的运算符具备处理所有比较的情况的能力。其重载方式也类似其他如=的运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass {
public:
// ...
auto operator<=>(const MyClass& other) const {
// 根据类的成员或某种逻辑来比较两个对象
if (/* some condition */) {
return std::strong_ordering::less;
} else if (/* equality condition */) {
return std::strong_ordering::equal;
} else {
return std::strong_ordering::greater;
}
}
};

3 枚举优化

3.1 支持特性的枚举

C++17 支持对枚举成员进行特性声明:

1
2
enum class Color { RED, GREEN, BLUE [[deprecated]] };
auto x = Color::BLUE; // compiler warning

3.2 switch中枚举的优化

C++20switch枚举使做出了优化, 可以通过using ...的方式简化枚举类型的声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// C++11 enum/enum class allows setting the underlying type
enum class Color : int { RED, GREEN, BLUE };

// C++20 allows introducing the enumerator identifiers
// into the local scope todecrease the verbosity
void printColor(Color color) {
switch (color) {
using enum Color;
case RED:
cout << "RED\n";
break;
case GREEN:
cout << "GREEN\n";
break;
case BLUE:
cout << "BLUE\n";
break;
}
}

4 更方便的流程控制

4.1 条件判断前的初始化

C++17允许在ifswitch前进行变量的初始化(类似golang的语法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void func1() {
// C++17 introduces if statement with initializer
int x = 3, y = 4;
if (int ret = x + y; ret < 10)
cout << ret << endl;

// C++17 introduces switch statement with initializer
switch (auto i = f(); x) {
case 3:
cout << i + x << endl;
break;
default:
exit(-1);
}
}

4.2 指定迭代范围的for循环

C++20引入了指定范围的for循环语法, 其支持自定义的初始化列表:

1
2
3
4
5
6
7
void func2() {
// C++20 introduces range-for loop statement with initializer
for (int i = 0; auto x : {'A', 'B', 'C'}) {
cout << i++ << ":" << x << " ";
}
cout << endl;
}

5 安全的联合体variant

C++17 引入了一个 std::variant 提供类型安全的联合体(Type-Safe Union)。std::variant 是一个模板类,它可以存储一组预定义的不同类型的数据,但是在任何时候仅能存储其中的一种类型,类似于传统的 C 语言中的 union,但它带有现代 C++ 的类型安全保证。

std::variant 提供了多种工具来管理和访问存储的值,例如:

  • index() 函数用于返回当前存储类型的索引。
  • holds_alternative<SomeType>() 用于检查当前存储的值是否为特定类型。
  • get<T>()get_if<T>() 方法用于访问存储的值;若试图访问错误的类型,则 get() 将抛出 std::bad_variant_access 异常,而 get_if() 则返回一个指向相应类型值的指针(若不匹配则返回 nullptr)。

案例代码:

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
#include <variant>
#include <string>
#include <iostream>

int main() {
// 定义一个 variant 类型,可以存储 int 和 std::string
std::variant<int, std::string> v;

// 初始化为 int 类型
v = 42;
std::cout << "v contains: ";
// 使用 std::visit 处理不同类型的值
std::visit([](auto&& arg) { std::cout << arg << '\n'; }, v);

// 改变 v 的内容为 string 类型
v = "Hello, World!";
std::cout << "v now contains: ";
std::visit([](auto&& arg) { std::cout << arg << '\n'; }, v);

// 检查并安全访问 variant 中的值
if (std::holds_alternative<std::string>(v)) {
std::cout << "The string in v is: " << std::get<std::string>(v) << '\n';
}

try {
// 错误的访问尝试:尝试获取 int 类型,但 v 现在存储的是 string 类型
// 这将会抛出 std::bad_variant_access 异常
std::cout << "The int in v is: " << std::get<int>(v) << '\n';
} catch (const std::bad_variant_access& e) {
std::cerr << "Error: Tried to access the wrong type in the variant: " << e.what() << '\n';
}

return 0;
}

6 避免unused warning

C++17C++26引入了更优雅的避免unused warning的手段:

1
2
3
4
5
6
7
void func3() {
int a = 3;
static_cast<void>(a); // C++17 以前的方式
[[maybe_unused]] int x = 5; // C++17
auto _ = 3; // C++26
cout << _ << endl;
}