现代c++实践100练:吃透c++新特性constexpr(代码片段)

CodeBowl CodeBowl     2022-12-23     466

关键词:

在讲这个问题之前,我想先说下本文的风格,这也是我写作的风格,通常我不会像教科书一般上来就直说概念和道理,我喜欢用已知,大家可以理解的问题去引入一个知识,这是我自己的学习方法,也希望介绍给大家。

例如本文,很多人可能不知道constexpr,也不知道为什么要引入它,但是大家都知道const,通过const引入,就往往容易接受的多。

本文你可以了解到什么

了解constexpr这一优秀的新特性

你可以知道我们为什么建议使用常量constexpr,它比const优秀?

const

const是一个C语言的关键字,它限定一个变量不允许被改变。

在之前const就是被作为常量使用的,现在多了一个constexpr是不是多此一举呢?

我们继续来看,了解一下const的作用:

(1)可以定义const常量,具有不可变性。

例如:const int Max=100; Max++会产生错误;

(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。

例如: void f(const int i) … 编译器就会知道i是一个常量,不允许修改;

(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。 同宏定义一样,可以做到不变则已,一变都变!

(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。

(5) 可以节省空间,避免不必要的内存分配。

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干份拷贝。

从上面几个地方看const就是常量,那么我们进行一个小测试:

const int len = 5;

int a[len];

可能在现在编译器使用,它都是不报错的,因为已经经过了编译器优化,但是在老版本的编译器中往往会报错!

这是因为 C++ 标准中数组的长度必须是一个常量表达式,而对 于 len而言,这是一个 const 常数,而不是一个常量表达式,因此(即便这种行为在大部分编译器中 都支持,但是)它是一个非法的行为。

如果你想知道,这到底是为什么,可以看下这段我的总结,但是我不保证它百分百正确!

数组的长度需要常量定义,像是5、6、7这种肯定是常量,只读;因为常量是被编译器放在内存中的只读区域,当然也就不能够去修改它。

而const变量实在内存中存在的,只不过编译器不允许它被修改,毕竟const定义的变量,虽然被叫做常量,但是更细一点是被叫做”常值变量“,当作变量看待。

constexpr:常量表达式

constexpr(常量表达式):是指值不会改变并且在编译过程就能得到计算结果的表达式。

常量表达式的优点是将计算过程转移到编译时期,那么运行期就不再需要计算了,程序性能也就提升了。

const好用,但是在某些情况下,我们还是会被它所谓的”常量“,给迷惑,产生错误用法,那既然如此,到底有没有真正的常量定义呢!

答案:有的。

C++11 提供了 constexpr 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这 个关键字明确的告诉编译器应该去验证 constexpr定义的值在编译期就应该是一个常量表达式。

constexpr int len = 5;

int a[len];

此时使用合法!

constexpr定义函数

constexpr int Length_Constexpr()

	return 5;


char arr_2[Length_Constexpr() + 1]; // 合法

constexpr返回值也是常量!

但是constexpr函数和正常函数肯定是不一样的,因为它需要在编译期做事,需要有一定的使用限制!

从C++11开始,constexpr函数不仅可以返回常量,还可以进行递归操作。

constexpr int fibonacci(const int n) 

return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);

从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句。

constexpr int fibonacci(const int n) 

if(n == 1) return 1;
if(n == 2) return 1;
return fibonacci(n-1) + fibonacci(n-2);

编译期的优化

C++ 本身已经具备了常量表达式的概念,比如 1+2, 3*4 这种表达式总是会产生相同的结果并且没 有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。

性能实测

constexpr int Calculate1 (int x, int y)

    return x * y * 32 / 14;

int Calculate2 (int x, int y)

    return x * y * 32 / 14;

int main()

    uint64_t start, end;
    start = GetMicroSeconds();
    for(int i = 0;i < 100000; i++)

//也可以换成const修饰,也是常量表达式
        constexpr int ret = Calculate1 (11, 12); 
    
    end = GetMicroSeconds();
    cout<<"spend time: "<<end-start<<" us"<<endl;
       
    start = GetMicroSeconds();
    for(int i = 0;i < 100000; i++)
    
//不能用constexpr修饰,不是常量表达式,会编译报错
        int ret = Calculate2 (11, 12);
    
    end = GetMicroSeconds();
    cout<<"spend time: "<<end-start<<" us"<<endl;
    return 0;

这段代码比较简单,一个常量表达式和一个非常量表达式,都进行相同的计算,都循环10w次,然后记录各自的总耗时,单位是微秒。

打印结果如下:

spend time: 182 us
spend time: 929 us

大家可以看到,接近5倍的性能差别,这要是发生在高性能开发中,将是一次不错的性能提升。

使用总结

语义上来说:

如果变量用constexpr修饰,那么变量也具有const的特性;如果变量用const修饰,不能说明变量具有constexpr的特性。从语义上讲,const更像是“read only”,而constexpr更像是“const”。

一般来说,当你认为变量一定是常量表达式,那就把它声明成constexpr类型吧。

参考资料

现代C++之constexpr

const和constexpr还在傻傻分不清?

现代c++实战100练:初始化列表(代码片段)

在讲这个问题之前,我想先说下本文的风格,这也是我写作的风格,通常我不会像教科书一般上来就直说概念和道理,我喜欢用已知,大家可以理解的问题去引入一个知识,这是我自己的学习方法,也... 查看详情

现代c++实践:变量声明强化,竟然可以在if/switch中定义变量!(代码片段)

变量声明强化,竟然可以在if/switch中定义变量!c通过本文你可以了解if/switch变量声明强化总结c通过本文你可以了解C++17新特性变量声明强化在if/switch中的变量声明强化if/switch变量声明强化在传统C++中,变... 查看详情

现代 C++ 编译器是不是能够避免在某些情况下两次调用 const 函数?

】现代C++编译器是不是能够避免在某些情况下两次调用const函数?【英文标题】:AremodernC++compilersabletoavoidcallingaconstfunctiontwiceundersomeconditions?现代C++编译器是否能够避免在某些情况下两次调用const函数?【发布时间】:2017-07-0219:4... 查看详情

技术解读:现代化工具链在大规模c++项目中的运用(代码片段)

编者按:C++语言与编译器一直都在持续演进,出现了许多令人振奋的新特性,同时还有许多新特性在孵化阶。除此之外,还有许多小更改以提高运行效率与编程效率。本文整理自全球C++及系统软件技术... 查看详情

c++最佳实践|3.安全性(代码片段)

...性、可移植性、多线程、性能、正确性等角度全面介绍了现代C++项目的最佳实践。本文是该系列的第三篇。C++最佳实践:1.工具2.代码风格3. 安全性(本文)4. 可维护性5. 可移植性及多线程6. 性能7. 正确性和... 查看详情

c++最佳实践|3.安全性(代码片段)

...性、可移植性、多线程、性能、正确性等角度全面介绍了现代C++项目的最佳实践。本文是该系列的第三篇。C++最佳实践:1.工具2.代码风格3. 安全性(本文)4. 可维护性5. 可移植性及多线程6. 性能7. 正确性和... 查看详情

提供可选参数的现代 C++ 方法

】提供可选参数的现代C++方法【英文标题】:ModernC++approachforprovidingoptionalarguments【发布时间】:2021-02-0304:29:03【问题描述】:让我们来看看下面的函数声明:voidprint(SomeTypeconst*i);这里,参数i的const*属性表明该参数的意图是可选... 查看详情

c++基础——c++相比c语言的新特性梳理总结(c++新特性输入输出方式命名空间namespace)(代码片段)

【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《QT开发实战》《嵌入式通用开发实战》《从0到1学习嵌入式Linux开发》《Android开发实战》《实用硬件方案设计》长期持续带来更... 查看详情

今晚7点半:现代c++和mediasoup的webrtc集群服务实践

...到了速航云创始人兼CEO闫华,分享去Node.js化后,现代C++和Mediasoup的WebRTC集群服务实践。 查看详情

C++ 函数体中的 const

】C++函数体中的const【英文标题】:constinbodyofC++functions【发布时间】:2013-09-2816:55:31【问题描述】:我意识到,如果我将constint定义到c++函数的主体中,然后使用地址算术来更改常量的值(它在堆栈上,不是吗?)。我得到了这... 查看详情

c++基础c++11的新特性(代码片段)

...csdn.net/dnty00/article/details/126171485文章目录一、C++11新特性1、语法的改进2、标准库扩充(往STL里新加进一些模板类,比较好用)二、智能指针一、C++11新特性C++新特性主要包括包含语法改进和标准库扩... 查看详情

c++基础三类和对象(中篇)(代码片段)

文章目录1.类的6个默认成员函数2.构造函数2.1概念2.2特性3.析构函数3.1概念3.2特性4.拷贝构造函数4.1概念4.2特性5.赋值运算符重载5.1运算符重载5.2赋值运算符重载5.3特性6.日期类的完整实现7.const成员7.1const修饰类的成员函数7.2请思... 查看详情

c++类和对象(代码片段)

...默认成员函数2.构造函数2.1构造函数的概念2.2构造函数的特性析构函数析构函数的概念析构函数的特性拷贝构造函数拷贝构造函数的概念拷贝构造函数的特性赋值运算符重载运算符重载赋值运算符重载const成员const修饰类的成员函... 查看详情

如何有效地为现代 C++ 中指向虚拟基类的指针向量分配空间

】如何有效地为现代C++中指向虚拟基类的指针向量分配空间【英文标题】:HowtoefficientlyallocatespaceforvectorofpointerstovirtualbaseclassinmodernC++【发布时间】:2015-06-1908:01:08【问题描述】:我有以下数据模型structBaseintx_;inty_;intz_;virtualintge... 查看详情

c++新特性11stdarray(代码片段)

1.std::array对象的大小是固定的,如果容器大小是固定的,那么可以优先考虑使用std::array容器。另外由于std::vector是自动扩容的,当存入大量的数据后,并且对容器进行了删除操作,容器并不会自动归还被删除... 查看详情

c++——引用内联函数(代码片段)

文章目录一、引用是什么?1.1引用的特性1.2常引用(对const的引用)1.3引用的使用场景1.4传值、传引用效率比较1.5引用和指针的区别二、内联函数1.什么是内联函数2.内联函数的特性三、auto关键字(C++11)auto的使用细则一、引... 查看详情

c++最佳实践|6.性能(代码片段)

...性、可移植性、多线程、性能、正确性等角度全面介绍了现代C++项目的最佳实践。本文是该系列的第六篇。C++最佳实践:1.工具2.代码风格3. 安全性4. 可维护性5. 可移植性及多线程6. 性能(本文)7. 正确性 查看详情

c++最佳实践|1.工具(代码片段)

...性、可移植性、多线程、性能、正确性等角度全面介绍了现代C++项目的最佳实践。本文是该系列的第一篇。C++最佳实践:1.工具(本文)2.代码风格3. 安全性4. 可维护性5. 可移植性及多线程6. 性能7. 正确性 查看详情