美文网首页C++C++
C++20:核心语言

C++20:核心语言

作者: 奇点创客 | 来源:发表于2020-02-22 20:06 被阅读0次

    原文详见:C++ 20: The Core Language

    在上篇文章 C++20:四大件 中,我们对概念(concepts)、范围(ranges)、协程(coroutines)以及模块(modules)做了简要的介绍。当然,C++20 提供了很多东西。今天,让我们来继续了解一下核心语言(Core Language)部分。

    核心语言(Core Language)

    通过查看上图,便可知道我将要介绍的特性。

    三路比较运算符 <=>

    三路比较运算符 <=> 通常被戏称为飞船运算符。它用来确定两个值 A 和 B 到底是 A < B , A = B 还是 A > B。
    你只需要礼貌地提出要求,编译器就可以自动生成三路比较运算符。在本例中,你将获得所有六个比较操作符,即 ==、!=、<、<=、> 和 >=。

    #include <compare>
    struct MyInt {
      int value;
      MyInt(int value): value{value} { }
      auto operator<=>(const MyInt&) const = default;
    };
    

    操作符 <=> 默认执行字典序比较,它按照基类从左到右的顺序,并按字段声明顺序对非静态成员进行比较。下面是一个摘自微软博客的相当复杂的例子:Simplify Your Code with Rocket Science: C++ 20's Spaceship Operator

    struct Basics {
      int i;
      char c;
      float f;
      double d;
      auto operator<=>(const Basics&) const = default;
    };
     
    struct Arrays {
      int ai[1];
      char ac[2];
      float af[3];
      double ad[2][2];
      auto operator<=>(const Arrays&) const = default;
    };
     
    struct Bases : Basics, Arrays {
      auto operator<=>(const Bases&) const = default;
    };
     
    int main() 
    {
      constexpr Bases a = { { 0, 'c', 1.f, 1. },
                            { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
      constexpr Bases b = { { 0, 'c', 1.f, 1. },
                            { { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
      static_assert(a == b);
      static_assert(!(a != b));
      static_assert(!(a < b));
      static_assert(a <= b);
      static_assert(!(a > b));
      static_assert(a >= b);
    }
    

    我认为,这个代码片段中最复杂的部分不是飞船运算符,而是它使用了聚合初始化来初始化 Bases。聚合初始化也就意味着:如果所有成员都是公有的,那么你可以直接初始化类类型(类、结构或联合)的成员。在本例中使用了大括号初始化列表(braced-initialisation-list)来做这件事。关于聚合初始化的细节详见:聚合初始化

    将字符串字面值作为模板参数

    在 C++20 之前,你不能使用字符串作为非类型模板参数。在 C++20 中,你可以使用它。其思想是使用标准定义的 basic_fixed_string 类型, basic_fixed_string 具有一个 constexpr 构造函数。constexpr 构造函数允许它在编译时实例化固定的字符串。

    template<std::basic_fixed_string T>
    class Foo {
        static constexpr char const* Name = T;
    public:
        void hello() const;
    };
    
    int main() 
    {
        Foo<"Hello!"> foo;
        foo.hello();
    }
    

    constexpr 虚函数

    由于动态类型是未知的,因此无法在常量表达式中调用虚拟函数。C++20 将沿用这个限制。

    指定初始化值

    让我先写一个使用聚合初始化的简单例子:

    // aggregateInitialisation.cpp
    #include <iostream>
    
    struct Point2D {
        int x;
        int y;
    };
    
    class Point3D {
    public:
        int x;
        int y;
        int z;
    };
    
    int main()
    {
        std::cout << std::endl;
       
        Point2D point2D {1, 2};
        Point3D point3D {1, 2, 3};
    
        std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
        std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
        
        std::cout << std::endl;
    }
    

    我认为没有必要解释这个程序。下面是这个程序的输出:

    显式胜于隐式。让我们看看这意味着什么。程序 aggregateInitialisation.cpp 中的初始化非常容易出错,因为你可能会在不经意间交换构造函数参数的顺序。下面所示的指定初始化值是从 C99 开始引入的。
    // designatedInitializer.cpp
    #include <iostream>
    
    struct Point2D {
        int x;
        int y;
    };
    
    class Point3D {
    public:
        int x;
        int y;
        int z;
    };
    
    int main()
    {
        std::cout << std::endl;
        
        Point2D point2D {.x = 1, .y = 2};
        // Point2D point2d {.y = 2, .x = 1};         // (1) error
        Point3D point3D {.x = 1, .y = 2, .z = 2};   
        // Point3D point3D {.x = 1, .z = 2}          // (2)  {1, 0, 2}
        
        std::cout << "point2D: " << point2D.x << " " << point2D.y << std::endl;
        std::cout << "point3D: " << point3D.x << " " << point3D.y << " " << point3D.z << std::endl;
        
        std::cout << std::endl;
    }
    

    Point2D 和 Point3D 实例的参数是被显式声明的。该程序的输出与程序 aggregationInitialisation.cpp 的输出相同。注释 (1) (2) 所在的行非常有趣,行 (1) 会产生错误,因为指示符的顺序与其声明顺序不匹配。行 (2) 中 y 的指定值缺失。在这种情况下,y 将被初始化为 0,就如同使用大括号初始化列表 { 1, 0, 3 } 的效果一样。

    Lambda 的各种改进

    Lambda 表达式将在 C++20 中进行多项改进。
    如果你想了解改进的详细信息,请转到 Bartek 的有关 C++17 和 C++20 中关于 lambda 改进的文章,或者等待我的详细文章。无论如何,我们将获得的两个有趣的变化:

    • 允许 [=, this] 作为 lambda 捕获器,并弃用隐式 this 捕获器 [=]
    struct Lambda {
        auto foo() {
            return [=] { std::cout << s << std::endl; };
        }
    
        std::string s;
    };
    
    struct LambdaCpp20 {
        auto foo() {
            return [=, this] { std::cout << s << std::endl; };
        }
    
        std::string s;
    };
    

    在 C++20 中,隐式 [=] 捕获器在 Lambda 结构中复制会引起一个弃用警告。当我们通过复制 [=, this] 显式捕获 this 对象时,在 C++20 中, 我们将不会在收到弃用警告。

    • 模板 lambda
      对此你的第一印象可能是:我们为什么需要模板 lambda?C++14 标准中当我们写下一个泛型 lambda: [](auto x){ return x; } 时,编译器会自动生成一个带有模板化的调用操作符的类:
    template <typename T>
    T operator(T x) const {
        return x;
    }
    

    但有时候,你想要定义一个仅适用于特定类型(如:std::vector)的 lambda 表达式。此时,模板 lambda 能帮我们达到这个目的。除了类型参数,你还可以使用一个概念:

    auto foo = []<typename T>(std::vector<T> const& vec) { 
            // do vector specific stuff
        };
    

    新属性:[[likely]] 和 [[unlikely]]

    使用 C++20,我们可以获取新的属性 [[likely]] 和 [[unlikely]] 。不管执行路径概率大小,这两个属性都允许它给优化器一个提示。

    for(size_t i=0; i < v.size(); ++i) {
      if (unlikely(v[i] < 0)) sum -= sqrt(-v[i]);
      else sum += sqrt(v[i]);
    }
    

    consteval 和 constinit 说明符

    新的说明符 consteval 用来创建了一个即时函数。即时函数指每次调用该函数都必须生成编译期常量表达式的函数。即时函数是隐式的 constexpr 函数。

    consteval int sqr(int n) {
      return n*n;
    }
    constexpr int r = sqr(100);  // OK
     
    int x = 100;
    int r2 = sqr(x);             // Error
    

    由于 x 不是常量表达式,因此 sqr(x) 不能在编译时执行 constinit 确保在编译时初始化具有静态存储期的变量,所以最后的赋值会出现错误。静态存储期意味着在程序开始时分配对象,在程序结束时释放对象。在命名空间范围内声明的对象(全局对象),使用 static 或 extern 声明的对象具有静态存储期。

    std::source_location

    C++11 中有 __LINE__ 和 __FILE__ 两个宏用于获取相应的信息。在 C++20 中,类 source_location 能给出关于源代码的文件名、行号、列号和函数名等信息。cppreference.com 上的这个简短的例子展示了它的第一种用法:

    #include <iostream>
    #include <string_view>
    #include <source_location>
     
    void log(std::string_view message,
             const std::source_location& location = std::source_location::current())
    {
        std::cout << "info: "
                  << location.file_name() << " : "
                  << location.line() << " "
                  << message << '\n';
    }
     
    int main()
    {
        log("Hello world!");  // info: main.cpp: 15 Hello world!
    }
    

    接下来?

    这篇文章是对核心语言特性的概述。下一篇文章我们将继续讲述 C++20 中的标准库特性。

    相关文章

      网友评论

        本文标题:C++20:核心语言

        本文链接:https://www.haomeiwen.com/subject/wmjkqhtx.html