原副标题:从 C++11 升级换代至 C++17,它让PDP控制系统更快了!
2011 年 8 月, ISO 理事会 正式发布了C++11,2017 年 12 月又正式发布了 C++17 国际标准,每天C语言新版本的插值,会因很多项目组也已经开始著手升级换代合作开发自然环境,比如责任编辑翻译者。所以从C++11 升级换代到
书名镜像:https://interrupt.memfault.com/blog/cpp-17-for-embedded
翻译者 | Çağlayan Dökme
翻译者 | 弯月 白眉林 | 郑丽媛
公司出品 | CSDN(ID:CSDNnews)
前段时间,他们项目组已经已经开始升级换代合作开发自然环境,试著采用很多辅助工具和编程语言的新版本。在那个操作过程中,较为十分困难的几项组织工作是将他们的PDP应用流程的标识符库从 C++11 升级换代到 C++17。
在责任编辑中,我将展现在PDP当今世界中十分管用的 很多C++17 的优点 (特别注意:从 C++11 北迁到 C++17 也囊括了 C++14,因而我也会提及 C++14 的很多优点)。
查看 完整的 C++17 特性列表,可前往:https://github.com/AnthonyCalandra/modern-cpp-features#c17-language-features。
C++14 的主要变化
当初,他们从 C++03 北迁到了 C++11,与之相比,从 C++11 升级换代到 C++14 时看到的升级换代较为小。因而,可以在PDP控制系统中采用的 C++14 特有功能实际上并不多。
二进制字面量
如果你经常需要执行按位运算或修改寄存器,所以一定很喜欢这些字面量。很多编译器具有支持此类字面量的扩展,这些字面量在实际的国际标准中也有一席之地。
uint8_ta = 0b110; // == 6 uint8_tb = 0b11111111; // == 255constexpr**
在 C++14 中,可以在 constexpr 函数中采用的语法得到了扩展。constexpr 特别适用于PDP合作开发,因为它可以在编译时进行计算并将很多标识符简化为常量。请特别注意,只有当表达式的所有需求都可以在编译期间确定时,才能在编译时计算表达式。
constexprintfactorial( intn) { if(n <= 1) { return1; } else{ returnn * factorial(n – 1); } } factorial( 5); // == 120 (Calculated at compile time)C++ 17 的当今世界
与 C++14 相比,C++17 国际标准有了很大的变化,但无需担心,你仍然可以采用已有的功能。除了已有功能之外,你还将拥有更强大的 C++17 语法和库。
(1)属性
首先,他们来介绍三个新属性:[[fallthrough]]、[[nodiscard]] 和 [[maybe_unused]]。因为这些属性只在编译时考虑,所以你根本不需要担心它的效率。它的存在就是为了提升标识符合作开发。
[[fallthrough]]
你可以利用那个属性将两个相邻的 case 分支的主体合并到一个 switch 中,而不会收到来自编译器的任何警告。你可以通过那个属性告诉编译器前一个case主体结束是有意为之。
switch(n) { case1: [[ fallthrough]] // … // no `break;` case2: // … break; }[[nodiscard]]
你是不是也经常忘记检查函数的返回值?有了那个属性,丢弃返回值就会收到编译器的警告。
[[nodiscard]] bool do_something{ returnis_success; // trueforsuccess, falseforfailure } do_something; /* warning: ignoring the returnvalue of functiondeclared with attribute nodiscard*/[maybe_unused]]
为了避免收到警告,必须将未采用的变量转换为 void,你是不是也感到不耐烦?试试看那个属性,你就可以摆脱那些烦人的警告。
voidmy_callback( std:: stringmsg, [[maybe_unused]] boolerror) { // Dont care if `msg` is an error message, just log it. log(msg); }(2)编译时的力量
编译时的检查是我最喜欢 C++ 的地方。在 C++17 中,这种能力通过很多新优点得到进一步增强。想一想很多PDP控制系统中繁琐的调试操作过程,如今甚至不需要部署标识符就可以检查结果,是不是觉得是个特大好消息?传输可执行文件、准备自然环境和测试等一系列组织工作都十分艰巨,而且很耗时。但采用编译时编程,这部分头疼的组织工作都可以省略。
没有消息的静态断言
你可能认为,他们已经有了 static_assert(..),可以在编译时进行检查。而如今,断言机制甚至不需要错误消息。这样,标识符看上去会更加清晰。
static_assert( false);if constexpr
我最喜欢的一个语句!他们可以利用 if constexpr 编写很多标识符,这些标识符可以根据编译时的条件,有选择地进行实例化。
template< typenameT> autolength( constT& value) noexcept{ ifconstexpr( std::integral<T>::value) { // is number returnvalue; } else{ returnvalue.length; } } intmainnoexcept{ inta = 5; std:: stringb = “foo”; std:: cout<< length(a) << << length(b) <<\n; // Prints “5 3” }在 C++17 之前,上面这段标识符需要编写两个不同的函数,分别用于字符串和整数输入,如下所示。
intlength( constint& value) noexcept{ returnvalue; } std:: size_tlength( conststd:: string& value) noexcept{ returnvalue.length;constexpr lambda
如果你也喜欢在标识符中采用 lambda 表达式,所以肯定会喜欢那个功能。此外,Lambdas 的调用也可以采用直接声明为 constexpr 的形式。
autoidentity = [](intn) constexpr{ returnn; }; static_assert(identity( 123) == 123);(3)语法糖
在 C++17 中,有很多功能可以帮助你编写更漂亮的标识符。即使它的存在对运行时性能没有明显的影响,但你会很喜欢它。
折叠表达式
如果你有过采用可变参数模板来编写具有可变输入或插值次数的递归算法的经历,所以就可能遇到必须为该可变参数模板函数实现终止符的问题。比如,下面的标识符是用 C++11 编写的,作用是累加给定的数字。
intsum{ return0; } // Termination function template< typename…Args> intsum( constint& arg, Args… args) { returnarg + sum(args…); }如果他们没有实现不接受任何输入的终止符,这段标识符将无法通过编译。但有了折叠表达式,你就不必实现终止符了,而标识符看上去也更快,如下所示。
template< typename…Args> intsum(Args&&… args){ return(args + …); }嵌套命名空间
不知道为什么 C++ 理事会以前没有想到这一点。无需多说,分别看下面 C++11 和 C++17 中嵌套命名空间的定义,你就能发现区别。
// C++11 namespaceA{ namespaceB{ namespaceC{ inti; } } } // C++17 namespaceA:: B:: C{ inti; }加强版的条件语句
如果所有条件语句都像 for 语句一样具有初始化,那是不是更强大?在 C++17 中,条件语句也增加了初始化部分。
这是迄今为止我所见过的最强大的功能之一,因为你无需在输入一系列 if-else 语句或 switch-case 之前,编写一堆局部变量。
if( inti = 4; i % 2== 0) { cout<< i << ” is even number”<< endl; } switch( inti = rand % 100; i) { default: cout<< “i = “<< i << endl; break; }内联变量
在 C++17 之前,他们必须在源文件中实例化类内静态变量。如今,你可以采用内联变量将声明和初始赋值合并到类定义中,如下所示。
structBabaMrb{ staticconstintvalue =10; staticinlinestd:: stringclassName = “Hello Class”; }(4)其他优点
C++17 中还有很多我不知道如何归类的的其他优点。下面,他们来逐一介绍。
复制省略
复制省略(Copy elision),即返回值优化,是大多数编译器为防止在某些情况下出现额外副本而实现的优化。从 C++17 已经开始,直接返回对象时必然会触发复制省略。在某些情况下,即使只有一次复制操作也会影响控制系统的性能,比如对实时性有严格要求的控制系统。遇到这种情况,他们最好确保避免复制,以免降低控制系统性能。
structC{ C { std:: cout<< “Default constructor”<<std:: endl; } C( constC&) { std:: cout<< “Copy constructor”<< std:: endl; } }; C f{ returnC; // Definitely performs copy elision } C g{ C c; returnc; // May perform copy elision } intmain{ C obj = f; // Copy constructor isnt called }共享互斥锁
在采用共享互斥锁后,他们就可以按需读取对象而无需加锁,而写调用可以照往常一样采用常规互斥锁来锁定对象。共享互斥锁可以加快只读访问操作的速度,因为读取操作可以同步进行。
硬件干涉大小
那个新的库功能可以帮助你在编译期间确定 L1 缓存行的大小。有了那个功能,你就可以根据 L1 缓存行的大小调整结构、缓冲区等。我在采用 C++11 为 ARM Cortex-A9 内核实现低级裸机 DMA 驱动流程时就会用到那个功能,因为在编写这些标识符时,我需要手动管理高速缓存和主内存之间的一致性。
尽管此功能十分强大,但直到版本 12 才在所有版本的 GCC 中实现,因而很可能你当前的编译器并不支持。如下标识符是一个示例,可以帮助你更快地理解那个功能。
# ifdef__cpp_lib_hardware_interference_size // Undefined prior to C++17 usingstd::hardware_constructive_interference_size; usingstd::hardware_destructive_interference_size; #else // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ … constexprstd::size_thardware_constructive_interference_size = 64; constexprstd:: size_thardware_destructive_interference_size =64; # endif struct alignas(hardware_constructive_interference_size)OneCacheLiner { // occupies one cache line std:: atomic_uint64_tx{}; std:: atomic_uint64_ty{}; };总结
与 C++14 不同,C++17 引入了很多新优点。其中很多功能对嵌入式控制系统合作开发十分有帮助。
不同产品之间,PDP设备的计算能力差异很大。由于 CPU 性能、缺乏编译器支持、验证必要性等多种原因,我选择的某些功能可能不适用于你的固件。总体而言,北迁到 C++17 可能需要花费大量的时间和精力,请认真考虑是否需要北迁。
▶ CentOS停服倒计时,如何打造安全好用的Linux控制系统?
▶ 首个国人主导的 Apache 数据集成顶级项目 SeaTunnel 毕业!
▶ 李彦宏宣布设立10亿创投基金促进大模型生态发展;Kindle中国电子书店停止运营;Bootstrap 5.3正式发布|极客头条

