C++20:核心语言

2019 年 11 月 12 日

C++20:核心语言

我上一篇文章 《C++ 20:四大巨头》首先概述了概念(Concepts)、范围(Ranges)、协程(Coroutines)和模块化(Modules)。当然,C++ 20 还提供了更多的功能。今天,我们将继续讲述关于核心语言的概述。


核心语言



当你看到这张图时,你就明白我想要介绍的功能了。


三元比较运算符<=>


三元比较运算符 <=> 通常被称为宇宙飞船运算符(spaceship operator)。该宇宙飞船运算符可用于确定两个值 A 和 B 的大小,是 A<B、A=B 还是 A>B。


编译器可以自动生成三元比较运算符。我们只需设置 default 就可以使用它了。在这种情况下,我们将得到全部共六个比较运算符,如 ==、!=、<、<=、> 和 >= 。


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


默认运算符 <=> 可以执行字典序比较,它按照基类从左到右的顺序,并按字段声明顺序对非静态成员进行比较。下面是一个摘自微软博客非常复杂的例子:用火箭科学简化你的代码:C++ 20 的宇宙飞船运算符


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);}
复制代码


我认为,这个代码片段中最复杂的内容不是宇宙飞船运算符,而是使用了聚合初始化来初始化 Base。聚合初始化本质上意味着:如果所有成员都是公共的,我们可以直接初始化类类型(class、struct 或 union)的成员。在这种情况下,我们可以使用带括号的初始化列表,如上例所示。这只是一个简化示例。请阅读此处的详细信息:聚合初始化


字符串文本作为模版参数


在 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 实例的参数是被显式声明的。该程序的输出与程序 aggregateInitialisation.cpp 的输出相同。注释掉的行(1)和(2)非常有趣。行(1)将会产生错误,因为指定元素的顺序与其声明顺序不匹配。 y 的指定值在行(2)中是缺失的。在这种情况下,y 将被初始化为 0,类似于使用带括号的初始化列表 {1、0、3} 对其进行初始化。


各种 Lambda 的改进


在 C++ 20 中, Lambda 将会有很多的改进。


如果你想了解更多变更详细信息,请浏览 Bartek 发表的关于 C++ 17 和 C++ 20 中的 Lambda 改进的文章,或者等待我编写更详细的文章。不管怎样,在此,我们将介绍 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 的第一印象可能和我的一样:我们为什么需要模版 Lambda ?当我们使用 C++ 14 中的 { return x; } 编写一个泛型 Lambda 时,编译器会自动生成一个带有模板调用运算符的类:


template <typename T>T operator(T x) const {    return x;}
复制代码


有时,我们想定义一个仅适用于特定类型(如 std::vector )的 Lambda。此时,模板 Lambda 可以帮我们解决这个问题。除了类型参数,我们还可以使用概念( concept ):


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: The Core Language


2019 年 11 月 12 日 11:276386

评论 4 条评论

发布
用户头像
2020 年 09 月 20 日 02:38
回复
用户头像
讲 C++ 的文章放了个 PHP 的头图……
2019 年 11 月 12 日 11:35
回复
多谢指正,已修改
2019 年 11 月 12 日 14:20
回复
😄
2019 年 11 月 25 日 11:25
回复
没有更多评论了
发现更多内容

【第一周作业】食堂就餐卡系统设计

黑莓

数据库周刊27丨6月最新国产数据库排行;OB成立新公司奥星贝斯;腾讯云发布图数据库TGDB;Oracle坏块修复;MySQL故障排查导图;经典SQL语句大全...

墨天轮

数据库

让独立思考成为习惯

Neco.W

学习 深度思考 思考

架构第一课学习总结

师哥

week01总结

seki

极客大学架构师训练营

架构师训练营--第1周总结感想

芥菜

小师妹学JavaIO之:NIO中Channel的妙用

程序那些事

io nio 小师妹 buffer channel

玄姐公开课总结【构建基于ServiceMesh的普适业务中台架构】

魔曦

架构 Service Mesh

Intellij IDEA 右击没有run

程李文华

系统/子系统/模块/组件/框架/架构

gen_jin

钟离昧的第一张架构设计图之旅

X中倪

食堂就餐卡系统设计文档

秤须苑

极客大学架构师训练营

食堂就餐卡设计说明书

架构方法:架构师如何做架构

架构师训练营第一周学习总结

常江舟

极客大学架构师训练营

量子技术到底有哪些突破值得重点关注?

蔡芳芳

week01作业

seki

架构师课程学习第一周心得

秤须苑

极客大学架构师训练营

Week 01 食堂饭卡系统设计

Geek_165f3d

架构师训练营-Week1-作业2

车小勺的男神

架构师训练营-Week1-作业1

车小勺的男神

SpringBoot分布式任务中间件开发 附视频讲解 (手把手教你开发和使用中间件)

小傅哥

小傅哥 中间件 springboot 分布式任务

02-kubernetes自建CA及双向TLS认证

绿星雪碧

Kubernetes TLS CA证书

游戏夜读 | 如何成长为游戏人?

game1night

钟离昧的一梭子架构师之旅

X中倪

架构师训练营作业一:食堂就餐卡系统设计

常江舟

极客大学架构师训练营

【第一周】学习总结

黑莓

Android 无埋点从入门到放弃:了解 Java 字节码

GrowingIO技术专栏

你并不理解i++和++i

flyhero

Java 程序员 JVM i++

使用VSCode连接到IBM Cloud区块链网络

程序那些事

智能合约 hyperledger fabric ibm cloud

LocalDateTime和Date的比较与区别

彭阿三

时间格式化 LocalDateTime Date

C++20:核心语言-InfoQ