介绍GCC8 中少数几个能用的C++20 特性

简介: 介绍GCC8 中少数几个能用的C++20 特性

第一章:引言

虽然GCC8开始支持C++20,但是大部分有用的功能集中在9-11,少数在12(比如Atomic std::shared_ptr and std::weak_ptr和Extending std::make_shared() to support arrays)和13(比如Text formatting和)

这些是C++20标准中的几个功能:

  1. Lambda Capture [=, this] (P0409R2): 允许在Lambda表达式中同时捕获外部作用域的所有变量(通过值)和当前类的this指针。
  2. VA_OPT (P0306R4, P1042R1): 提供了一种在宏中使用可选的变量参数列表的方法,主要用于预处理器。
  3. Designated Initializers (P0329R4): 允许使用指定初始化器,这是一种更清晰地初始化结构体或数组的方法。
  4. Template-parameter-list for Generic Lambdas (P0428R2): 允许在泛型Lambda表达式中显式指定模板参数列表,提供了更大的灵活性。
  5. Default Member Initializers for Bit-fields (P0683R1): 允许为位域成员在类定义中指定默认初始值。
  6. Initializer List Constructors in Class Template Argument Deduction (P0702R1): 改进了类模板参数推导,特别是涉及到初始化列表构造函数的情况。
  7. Simplifying Implicit Lambda Capture (P0588R1): 简化了Lambda表达式中隐式捕获的规则,使代码更清晰、易于理解。
  8. Relaxing the Structured Bindings Customization Point Finding Rules (P0961R1): 放宽了结构化绑定自定义点查找规则,提高了其灵活性和使用范围。
  9. Relaxing the Range-for Loop Customization Point Finding Rules (P0962R1): 类似地,放宽了范围for循环的自定义点查找规则。
  10. Allow Structured Bindings to Accessible Members (P0969R0): 允许结构化绑定访问可访问的成员,进一步增强了结构化绑定的功能。
  11. Utility to Convert a Pointer to a Raw Pointer (P0653R2): 这项特性提供了一种将智能指针(如std::unique_ptrstd::shared_ptr)转换为原始指针的实用工具。这有助于在需要原始指针的API中使用智能指针。
  12. std::variant and std::optional Should Propagate Copy/Move Triviality (P0602R4): 这个提案旨在使std::variantstd::optional在复制和移动操作中保持平凡(trivial)。当它们包含的类型是平凡可复制或可移动的时,std::variantstd::optional也应该是平凡的。(GCC8.3)
  13. Make create_directory() Intuitive (P1164R1): 这个改进使得std::filesystem::create_directory()函数更直观。在更早的标准中,如果路径的父目录不存在,此函数可能会失败。新提案使其行为更符合直觉,即在需要时自动创建父目录。(GCC8.3)

这些特性都是为了提高C++标准库的实用性和直观性。更多细节可以在C++20的官方文档中找到。

这些特性进一步增强了C++的现代编程能力和灵活性。更详细的信息可以在C++20的官方文档中找到。

第二章: Lambda Capture [=, this]

在C++20中,引入了一种新的Lambda捕获方式,即“Lambda Capture [=, this]”。这种新特性允许在Lambda表达式中更灵活地结合“按值捕获”和“按this指针捕获”。

2.1 Lambda表达式的捕获列表

Lambda表达式是C++中一个强大的特性,它允许我们定义匿名函数。在Lambda表达式中,捕获列表定义了哪些外部变量可以在Lambda函数体内使用。例如,[=]表示按值捕获所有外部变量,而[&]表示按引用捕获。

2.1.1 捕获列表的选择

选择正确的捕获方式对于确保代码的效率和安全性至关重要。过度使用按引用捕获可能导致悬挂引用,而不必要的按值捕获可能导致额外的内存开销。

2.2 使用 [=, this] 的场景

[=, this]捕获方式结合了按值捕获外部变量和按this指针捕获的优势,特别适用于需要访问类成员变量和外部变量的场景。

2.2.1 示例代码

考虑一个场景,我们有一个类成员函数,需要在其中创建一个Lambda表达式,这个Lambda既需要捕获类的成员变量,又需要捕获外部的局部变量:

class MyClass {
    int memberVar = 10;
    void myFunction() {
        int localVar = 20;
        auto myLambda = [=, this]() {
            return localVar + this->memberVar;  // 使用外部变量和类成员
        };
        std::cout << myLambda() << std::endl; // 输出30
    }
};

在这个例子中,Lambda通过[=, this]捕获方式,既能访问局部变量localVar(按值捕获),又能安全访问类成员变量memberVar

2.2.2 为什么选择 [=, this]

选择[=, this]的主要考虑是使用灵活性和性能的平衡。在一些情况下,我们可能并不需要类的所有成员,但对于局部变量的访问却是必需的。在这种情况下,[=, this]提供了一种既简洁又高效的方式。

2.3 深入理解和应用

正确使用Lambda表达式的捕获列表,可以使代码更加简洁、高效且安全。Lambda表达式的灵活性允许我们在不牺牲性能的前提下,编写出更加精炼和直观的代码。在选择捕获列表时,理解其背后的含义和影响,能帮助我们更好地利用这一强大的语言特性。

第三章: VA_OPT

C++20中引入了一个新的预处理器功能:__VA_OPT__。这个功能提供了在宏定义中处理可变数量参数的增强灵活性。

3.1 __VA_OPT__的基本概念

__VA_OPT__允许在宏中条件地包含或排除代码片段,这是基于是否有可变参数传递给宏。它特别适用于需要根据参数数量变化的宏定义。

3.1.1 使用场景

在创建可以处理不同数量参数的宏时,__VA_OPT__显得尤为有用。比如,当需要根据传入参数的不同,调整宏的行为时,它提供了一种简洁的方式。

3.2 示例代码

下面的示例展示了如何使用__VA_OPT__来创建一个宏,该宏根据是否有额外的参数来调整其行为:

#define MY_MACRO(...) printf(__VA_OPT__(extra args: ) __VA_ARGS__)
MY_MACRO("Hello");        // 输出: Hello
MY_MACRO("Hello", "World"); // 输出: extra args: Hello, World

在这个示例中,如果MY_MACRO被传递了超过一个参数,__VA_OPT__会激活并打印"extra args: "。

3.2.1 灵活性分析

通过使用__VA_OPT__,我们能够创建更加灵活和强大的宏,这可以适应各种不同的编程情况。它减少了需要编写多个不同宏的需求,简化了代码的管理和使用。

第四章: Designated Initializers

C++20引入了“指定初始化器”(Designated Initializers),这是一种在初始化结构体或类时提供更明确的语法。

4.1 指定初始化器的核心概念

指定初始化器允许在初始化时直接指定要初始化的成员的名称和值,从而提供更清晰和更直观的初始化方式。

4.1.1 使用场景与优势

当结构体或类包含多个成员时,指定初始化器可以清晰地表达每个成员的初始值,减少错误并增强代码的可读性。

4.2 示例代码

struct MyStruct {
    int x;
    double y;
    const char* z;
};
MyStruct obj = {.x = 1, .y = 2.0, .z = "Example"};  // 使用指定初始化器

在这个示例中,MyStruct的每个成员都通过其名称被显式初始化,这样做提高了代码的清晰度和易读性。

4.2.1 对比传统初始化

与传统的初始化方法相比,指定初始化器提供了一种更直接和更容易理解的方法来初始化结构体或类的成员。

第五章: 泛型Lambda的模板参数列表

C++20为泛型Lambda引入了显式的模板参数列表特性,增强了Lambda表达式的灵活性和表达能力。

5.1 泛型Lambda与模板参数列表

泛型Lambda允许Lambda表达式处理不同类型的参数,而引入模板参数列表则让程序员能更精确地控制Lambda的类型行为。

5.1.1 应用场景

当需要在Lambda中显式指定类型约束或使用多个不同类型参数时,模板参数列表变得非常有用。

5.2 示例代码

auto lambda = []<typename T>(T a, T b) { return a + b; };

在这个例子中,Lambda通过模板参数T定义,可以对任何相同类型的ab执行操作。

5.2.1 与传统Lambda的对比

相比于传统Lambda,引入模板参数列表的Lambda在处理多类型数据时更加灵活和强大。

第六章: 位字段的默认成员初始化器

C++20为位字段(bit-fields)增加了默认成员初始化器的功能,进一步提升了类设计的灵活性和表达力。

6.1 位字段的默认初始化

位字段默认初始化允许在类定义中直接为位字段成员指定初始值。这简化了对具有默认值需求的位字段的初始化处理。

6.1.1 使用场景

在需要为位字段成员提供初始设定值的场景中,这个特性极大地简化了代码的编写和维护。

6.2 示例代码

class MyClass {
    int myBitField : 3 = 7;
};

这里,myBitField是一个占用3位的位字段,它被默认初始化为7。

6.2.1 对比传统做法

在C++20之前,初始化位字段需要在构造函数中进行,这样的默认成员初始化器提供了一种更直接和便捷的方法。

第七章: 类模板参数推导中的初始化列表构造函数

C++20中的一个重要特性是在类模板参数推导(Class Template Argument Deduction, CTAD)中引入了对初始化列表构造函数的支持。

7.1 初始化列表在类模板参数推导中的作用

这个特性使得在使用初始化列表时,编译器能够自动推导出模板参数的类型,从而简化了类模板对象的创建过程。

7.1.1 使用场景

当使用类似于std::vector这样的容器类时,这个特性尤其有用,因为它允许直接通过初始化列表来创建对象,而不需要显式指定模板参数类型。

7.2 示例代码

std::vector myVec = {1, 2, 3}; // C++20允许这样的写法,自动推导为std::vector<int>

在这个例子中,编译器会自动推导出myVecstd::vector<int>类型。

7.2.1 对比传统做法

与传统需要显式指定模板参数的方法相比,这种自动推导大大简化了代码,尤其是在处理复杂的模板类型时。

第8章: 简化Lambda隐式捕获

8.1 理解隐式捕获的本质

在C++中,Lambda表达式提供了一种方便的方式来定义匿名函数。隐式捕获(Implicit Lambda Capture)允许Lambda表达式自动捕获所需的外部变量。然而,传统的隐式捕获方式可能会造成一些理解上的困扰,尤其是在捕获列表中同时出现[=]this时。

8.1.1 示例与分析

考虑以下传统的隐式捕获示例:

class MyClass {
    int x = 10;
public:
    void myFunction() {
        auto lambda = [=]() { return x; }; // 传统方式
    }
};

在这个例子中,Lambda表达式通过[=]捕获所有外部变量,包括类成员x。但这种方式并不直观,因为对于类成员的访问,实际上是通过this指针间接进行的。

8.2 C++20中的改进

C++20通过P0588R1提案,引入了一种更直观的方式来处理这个问题。

8.2.1 改进后的示例

在C++20中,你可以直接指定[=, this]来强调this指针的捕获:

class MyClass {
    int x = 10;
public:
    void myFunction() {
        auto lambda = [=, this]() { return x; }; // C++20 改进
    }
};

在这里,[=, this]明确了this指针的捕获,增加了代码的清晰度和直观性。这种改进在编程实践中非常有用,特别是在处理涉及类成员的复杂Lambda表达式时。

8.2.2 使用场景

这种改进在需要明确区分类成员和其他外部变量的场景下尤其有用。例如,在设计模式(如观察者模式)中,Lambda表达式经常用于回调和事件处理,这时候清晰地标示出捕获的this指针,能够帮助开发者更好地理解和维护代码。

通过这些改进,C++20使Lambda表达式的使用变得更加灵活和直观,同时也体现了C++标准的不断进化,旨在提供更高效、更清晰的编程工具。

第9章: 放宽结构化绑定自定义点查找规则

C++20中的一个重要改进是放宽结构化绑定(Structured Bindings)的自定义点查找规则。这一改进主要体现在如何更灵活地处理结构化绑定,使得开发者在使用结构化绑定时有更多的控制权和灵活性。

9.1 结构化绑定的改进

在之前的C++标准中,结构化绑定对于如何解包(unpack)对象有着较严格的规则。新的提案放宽了这些规则,使得开发者可以更容易地定义自己的解包逻辑。

9.1.1 使用场景与示例

struct MyStruct {
    int x;
    double y;
};
MyStruct s{5, 3.2};
// C++17
auto [a, b] = s; // 结构化绑定
// C++20中放宽的规则可能允许更多自定义解包行为

在这个例子中,C++20的改进允许MyStruct类型有更灵活的结构化绑定行为。

9.2 对开发者的意义

放宽结构化绑定规则意味着更大的灵活性。开发者可以根据自己的需求定制结构化绑定的行为,使代码更加符合特定场景的需求。这不仅提高了代码的可读性,也使得代码更加灵活和强大。

通过这些改进,C++20继续展现了其作为一门现代高效编程语言的实力和灵活性。

第10章: 放宽范围for循环自定义点查找规则

10.1 改进范围for循环

C++20对范围for循环(Range-for Loop)的自定义点查找规则进行了放宽。这项改进增加了for循环在处理自定义迭代器和范围时的灵活性。

10.1.1 背景与示例

在以前的版本中,使用范围for循环时,必须严格遵循一定的迭代器和结束条件。C++20的改进意味着可以更灵活地定义这些条件,使for循环能够更容易地适应复杂的或自定义的数据结构。

// 示例: 自定义迭代器和结束条件的范围for循环
for (auto element : customRange) {
    // 处理元素
}

10.2 对开发者的益处

这一改进使得开发者可以为特定类型或数据结构定制迭代器行为,增强了C++的灵活性和表达力,同时也提高了代码的可读性和可维护性。通过这种方式,C++继续其作为一种强大、现代的编程语言的发展道路。

第11章: 允许结构化绑定访问可访问成员

11.1 结构化绑定的发展

C++20中的一个重要改进是允许结构化绑定(Structured Bindings)直接访问对象的公共成员。这一改进提高了结构化绑定的灵活性和实用性。

11.1.1 改进的实际应用

在此改进之前,结构化绑定仅限于解包带有非静态成员的类或结构体。C++20的改进使得可以直接通过结构化绑定访问对象的公共成员。

struct MyStruct {
    int x;
    double y;
};
MyStruct s{5, 3.2};
auto [a, b] = s; // 直接绑定到x和y

11.2 对代码清晰度的提升

这一改变使得结构化绑定更加直观和易于使用,尤其是在处理复杂数据结构时。通过这种方式,C++再次证明了其作为一种现代和强大的编程语言的能力。

第13章: std::variant和std::optional应传播复制/移动的平凡性

C++20中的一个重要改进是让std::variantstd::optional能够传播其包含的类型的复制和移动操作的平凡性(Triviality)。这意味着,如果std::variantstd::optional包含的类型具有平凡的复制或移动构造函数,那么std::variantstd::optional自身也将具有相应的平凡构造函数。这个改进简化了对这些类型的操作,提高了效率,并有助于优化性能,特别是在涉及大量对象复制或移动的场景中。

第14章: 使create_directory() 更直观

C++20的一个重要改进是使std::filesystem::create_directory()函数更加直观和易于使用。在此之前,如果目标目录的父目录不存在,该函数可能会失败。新的提案使其行为更符合直觉,即在需要时自动创建父目录,无需手动创建。这个改进使文件和目录操作更容易理解和管理,特别是在处理文件系统时。这有助于简化文件操作代码,提高代码的可读性和可维护性。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
1月前
|
算法 编译器 C语言
【C/C++ 编译器的差异化】C++标准库在GCC和VS之间的表现差异
【C/C++ 编译器的差异化】C++标准库在GCC和VS之间的表现差异
149 1
|
1月前
|
安全 编译器 C语言
【C++ 编译器 版本支持】深度解读C++ 版本以及编译器版本相关宏
【C++ 编译器 版本支持】深度解读C++ 版本以及编译器版本相关宏
54 0
|
10月前
|
运维 Linux Go
嵌入式linux下的c语言日志log模块,功能增强(二)
嵌入式linux下的c语言日志log模块,功能增强(二)
|
算法 IDE 编译器
基于GCC的编译器的优化等级的执行原理
基于GCC的编译器的优化等级的执行原理
214 3
基于GCC的编译器的优化等级的执行原理
|
存储 Linux C语言
嵌入式Linux系统中ARM汇编语言的使用方法
大家好,今天主要大家聊一聊,如何在ARM中使用汇编语言的方法。
171 0
嵌入式Linux系统中ARM汇编语言的使用方法
|
编译器 Linux API
一个问题引出的---对gcc与C语言标准思考
最近在总结关于Linux系统关于Time处理相关的API,当在开发库中使用到localtime_r以及clock_gettime时,会提示如下的错误(-Werror选项打开):
507 0
|
编译器 iOS开发 Windows
带你读《LLVM编译器实战教程》之一:构建和安装LLVM
本书的前半部分将向您介绍怎么样去配置、构建、和安装LLVM的不同软件库、工具和外部项目。接下来,本书的后半部分将向您介绍LLVM的各种设计细节,并逐步地讲解LLVM的各个编译步骤:前段、中间表示(IR)、后端、即时编译(JIT)引擎、跨平台编译和插件接口。本书包含有大量翔实的示例和代码片段,以帮助读者平稳顺利的掌握LLVM的编译器开发环境。
19419 0
|
编译器 C++ 前端开发
带你读《LLVM编译器实战教程》之三:工具和设计
本书的前半部分将向您介绍怎么样去配置、构建、和安装LLVM的不同软件库、工具和外部项目。接下来,本书的后半部分将向您介绍LLVM的各种设计细节,并逐步地讲解LLVM的各个编译步骤:前段、中间表示(IR)、后端、即时编译(JIT)引擎、跨平台编译和插件接口。本书包含有大量翔实的示例和代码片段,以帮助读者平稳顺利的掌握LLVM的编译器开发环境。
|
前端开发 rax C语言