美文网首页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:标准库

    原文详见:C++20: The Library 在上篇文章 C++20:核心语言 中我们介绍了 C++20 的核心...

  • C++20:核心语言

    原文详见:C++ 20: The Core Language 在上篇文章 C++20:四大件 中,我们对概念(co...

  • C++20:四大件

    C++20 (C++ 编程语言标准 2020 版) 将是 C++ 语言一次非常重大的更新,将为这门语言引入大量新特...

  • go-rust-string操作

    在没有gc的语言里,c语言太老,在应用开发中工程化太弱。c++ c++11/c++14/c++17/c++20,不...

  • C++20:并发

    原文详见:C++20: Concurrency 本篇是 C++20 概览系列的最后一篇。今天我将介绍 C++ 新标...

  • C++20:概念之细节

    原文详见:C++20: Concepts, the Details 在我的上一篇文章 C++20:两个极端和概念的...

  • C++20:两个极端与概念的救赎

    原文详见:C++20: Two Extremes and the Rescue with Concepts 我们在...

  • C++20学习:基于Ubuntu系统编译gcc10.2.0

    问题 c++20标准已经发布,c++20有比较多的新特性。想尝个先,虽然目前还没有一个编译器能够完全支持c++20...

  • C++20 读书笔记(1)

    最近在看C++20相关的内容,本篇记录下遇到的比较好用的特性 Module C++20新增的4个大特性之一,Mod...

  • C++雾中风景18:C++20, 从concept开始

    转眼间,C++20的标准已经发布快两年了。不少C++的开源项目也已经将标准升级到最新的C++20了,笔者也开启了新...

网友评论

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

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