const 和 constexpr

c++中的constexpr:

  1. 编译时确定和运行时确定: 编译时确定如 const int a  = 3;   运行时确定如 const int b = func();   其中a就是编译时确定,b是运行时确定;

    但是当func()的函数实现是 func(){return 3;},返回的是一个常量值,这个时候如果用const来接收,依然是运行时确定,如果用constexpr来接收这个函数返回值,constexpr int b  = func(); 在编译阶段编译器就可以确定其常量属性。

    constexpr显示的告诉编译器,这个值是常量的。但是如果func的函数内返回的不是常量,constexpr int func(){ return func2(); } func2返回一个变量值,这个时候虽然指定了constexpr,但是确定常量值的时间依然会推迟到运行时。

       constexpr的作用:将常量表达式的常量值信息提前到编译阶段确认。

  2.constexpr修饰的指针表示的是顶层指针,对指针有效。

    3.c++11 指定函数返回值和参数必须保证是字面量,并且只有一个return, 不能是虚函数;c++14 相对于c++11不必保证只有一个retuan;(一个return的函数在复杂情况下可以用三元运算符或者递归计算来返回值)

   const和constexpr修饰变量时:const表达式可以在运行时再确定常量属性,constexpr修饰的表达式在编译器确定常量属性(常量的值)。

   const和constexpr修饰函数时:constexpr函数参数如果不能在编译器确定常量属性,则相当于const

   const和constexpr修饰类时:尽管构造函数不能是const的,但是字面值常量类的构造函数可以是constexpr函数。事实上,一个字面值常量类必须至少提供一个constexpr构造函数。

    constexpr构造函数可以声明成=default的形式(或者是删除函数的形式)。否则,constexpr构造函数就必须既符合构造函数的要求(意味着不能包含返回语句),又符合constexpr函数的要求(意味着它能拥有的唯一可执行语句就是返回语句)。

    综合这两点可知,constexpr构造函数体一般来说应该是空的,因此对函数成员的初始化必须放在初始化列表中。constexpr构造函数必须初始化所有数据成员,constexpr构造函数保证了传递给它的所有参数都是constexpr类型的,产生的对象的所有成员也都是constexpr

 

 

参考地址:https://www.jianshu.com/p/34a2a79ea947  

        https://blog.csdn.net/qq_37653144/article/details/78518071

以下内容转: https://www.cnblogs.com/wangxiaobao/p/5966394.html

 

Scott Meyers在effective modern c++中提到“If there were an award for the most confusing new word in C++11, constexpr would probably win it.”

由此可见,constexpr确实是比较难以让人理解。加之其在C++11和14中的标准略有不同,也加剧了这种难度。

参考几本经典教材(C++ primer, effective modern C++, a tour of C++)以及蓝色大大在知乎上的一些解答,整理出constexpr的用法和注意事项。

1.概念,constexpr objects

C++ primer中给出的定义是 “常量表达式是指不会改变并且在编译过程中就能得到计算结果的表达式 【1】。”

可以理解为在const上又加一层限定条件,即const并不限定是编译期常量还是运行期常量,而constexpr必须是编译期常量(在编译阶段得到结果)

举例如下:

众所周知,array的size是需要在编译期确定的,所以当其size不是一个常量表达式时,是无法通过编译的。

int i;
const int size = i;
int arr[size];         //error,size不是常量表达式,不能在编译期确定

而如果size是一个constexpr变量,则符合编译期确定的条件,可以通过编译。

constexpr auto size = 10;
int arr[size];                     //OK,size时常量表达式

当然,要定义一个常量表达式的时候,也要确保其右侧是常量表达式,否则该处便无法通过编译。

int i;
constexpr int size = i;  // error,i不能在编译期确定

所以用effective modern c++中的一句话总结这一部分就是:

“constexpr objects are const and are initialized with values known during compilation【2】”.

 

2. constexpr functions

比起constexpr变量,用constexpr修饰的函数有些更容易混淆的地方。

1) constexpr修饰的函数,当传入参数是可以在编译期计算出来时,产生constexpr变量;当传入参数不可以在编译期计算出来时,产生运行期遍历(constexpr等于不存在)

因此,不必写两个函数,如果函数体存在constexpr适用条件,就应该加上constexpr关键字。

例如(例子来源【3】):

复制代码
constexpr int foo(int i) {
    return i + 5;
}

int main() {
    int i = 10;
    std::array<int, foo(5)> arr; // OK,5是常量表达式,计算出foo(5)也是常量表达式
    
    foo(i); // Call is Ok,i不是常量表达式,但仍然可以调用(constexpr 被忽略)
    
    std::array<int, foo(i)> arr1; // Error,但是foo(i)的调用结果不是常量表达式了
   
}
复制代码

2) 在C++11和14中的区别

在C++11标准中,对于constexpr修饰的函数给了及其苛刻的限定条件:函数的返回值类型及所有形参的类型都是字面值类型,而且函数体内必须有且只有一条return语句【1】

这个条件显然是太苛刻了,以至于很多在constexpr的操作都要借助?:表达式,递归等办法实现。

在C++14中,放宽了这一限定,只保留了“函数的返回值类型及所有形参的类型都是字面值类型”,也就是说,这些值都在编译期能确定了就行。

 

3. constexpr class(字面值常量类)

built-in类型是字面值常量,但是有时需要自定义类型也作为字面值常量,这时候就需需要将constexpr修饰构造函数。

字面值常量类必须至少提供一个constexpr构造函数。

例如:

复制代码
class Point {
public:
    constexpr Point(double xval = 0, double yval = 0): x(xval), y(yval) { }
    constexpr double getX() const {return x;}
    constexpr double getY() const {return y;}
private:
    double x,y;
};
复制代码

当这样定义一个类后,便可以将Point类型的对象定义为字面值常量。即:

复制代码
constexpr Point p1(9.4, 27,7);
constexpr Point p2(28.8, 5.3);

constexpr
Point midpoint(const Point& p1, const Point& p2) {
    return {p1.getX() + p2.getX() / 2, p1.getY() + p2.getY() / 2} ;
}
 
constexpr auto mid = midpoint (p1, p2);
复制代码

上述例子中,p1,p2均为字面值常量,midpoint为constexpr修饰的函数,所以���取mid的整个过程均在编译期就可以完成,软件运行的时间自然会大大减少。

至此关于constexpr的三个主要用途(constexpr变量,constexpr修饰函数,constexpr修饰构造函数)就总结完毕,下面是一些注意事项。

 

注意事项1: 很多人(包括我自己)在gcc中验证数组大小必须在编译期指定的例子时发现:

如果array定义在主函数内,即使给定的不是一个常量表达式,也可以通过编译。这差点颠覆了我的认知。。。

蓝色大大在知乎答案【4】中解释了这一点,其实是C99中的variable length array。在全局变量中不能使用(无法分配内存),在局部变量中可以使用,细节可以参考那份解答。

注意事项2:constexpr这么复杂,到底为什么要用? 

其实第一还是为了效率。效率是C++的设计哲学之一,编译期可以确定的东西,便可以提醒编译期优化,也可能存放在read-only memory中

第二就是这样声明的constexpr变量便可以用在诸如上述数组长度指定,还有包括模板参数,case标签等场合,会便于使用【5】

 

参考资料:

1. Stanley B. Lippman / Josée Lajoie / Barbara E. Moo,  C++ Primer 中文版(第 5 版)[M].  电子工业出版社,2013

2. Meyers S. Effective Modern C++[M]. O'Reilly, 2014.

3. 蓝色在知乎问题“C++ const 和 constexpr 的区别?”中的解答: https://www.zhihu.com/question/35614219

4. 蓝色在知乎问题“constexpr和const数组的区别?”中的解答: https://www.zhihu.com/question/29662350/answer/45192834

5. Stroustrup B. A Tour of C++[M]. Addison-Wesley Longman, Amsterdam, 2013.

posted @ 2019-06-01 11:51  QiangAnan  阅读(560)  评论(0编辑  收藏  举报