constexpr:确定性的常量优化

从C++11开始,规定允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

constexpr int a = 3; //3是常量表达式
constexpr int b = a+1; //b是常量表达式
constexpr int c = get_size(); //只有当get_size()是一个constexpr函数时,才是常量表达式,否则语句错误

所以对于修饰变量的情况,相对于constexpr,const更类似于C#中的readonly——仅保证对象不被修改,而对象到底是在编译期还是在运行期求值都无所谓;而constexpr仅针对编译期求值。

const修饰指针变量时:

  1. 只有一个const,如果const位于*左侧,表示指针所指数据是常量,不能通过解引用修改该数据;指针本身是变量,可以指向其他的内存单元。
  2. 只有一个const,如果const位于*右侧,表示指针本身是常量,不能指向其他内存地址;指针所指的数据可以通过解引用修改。
  3. 两个const,*左右各一个,表示指针和指针所指数据都不能修改。

需要注意的是,与const关键字不同,一个指针被定义为constexpr,关键字仅对指针有效,与指针所指的对象无关。constexpr指针的初始值受到严格的限制。一个constexpr指针的初始值必须是nullptr或者0,或者是像先前说的一样是存储某个固定地址的对象。

constexpr函数所定义的是编译期的函数,但实际上在运行期constexpr函数也能被调用。事实上,如果使用编译期常量参数调用constexpr函数,我们就能够在编译期得到运算结果;而如果使用运行期变量参数调用constexpr函数,那么在运行期我们同样也能得到运算结果。因此在C++11之后进行数值计算时,无论在编译期还是运行期我们都可以统一用一套代码来实现,而不像以前一样需要使用模板。编译期和运行期在数值计算这点上得到了部分统一。因为constexpr可在编译期求值的特性,所以它的返回值也是常量表达式,可以用于描述数组尺寸等需要使用常量表达式的工作。

在类中,constexpr还带来了一个非常良好的功能——可以让我们自定义字面常量类。我们知道C++中的内建数据类型都可以写成编译期求值的字面量。通过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;
};

这样定义之后,该类的对象就可以参与进常量表达式:

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 Point mid = midpoint (p1, p2);

这里的p1,p2均为字面值常量,midpoint为constexpr修饰的函数,所以求取mid的整个过程均在编译期就可以完成,程序运行的时间自然会大大减少。

有人对constexpr存在的必要性有疑问,从保证程序员不会误修改常量这个作用来看,const和constexpr并没有什么区别。我们又知道,对于常量表达式,编译器一般会进行常量传播、常量折叠等优化操作。那么使用constexpr显式指定常量优化有什么必要性呢?

为了回答这个问题,我们需要对常量优化有一个基本认识。比如对于如下代码:

a = 100
b = 200
x = b + 1
b = 123
y = a + b

通过到达定值分析可以知道, a在d5处的值就是100;而d2虽然没能到达d5,但d4能到达d5,又可以看到d4的右值也是个常量,那么b在d5处的值无疑就是123,所以我们可以放心大胆地把y = a + b优化成y = 223。这没有任何问题。然而在实际程序语言中,我们无法忽略别名这一情况。过程参数、数组访问和间接引用都可以产生别名,因此指出一个语句是否向特定变量赋值并不是件很容易的事情。别名的引入使得分析工作复杂了很多,因为指针等本体-实体分离的数据类型的存在,归纳一个实体的所有别名就涉及指针分析这种不可判定问题。因此在一个复杂的系统中,几乎不可能通过程序分析分辨一个初始值到底是不是常量表达式。因此也无法做到没有指向性的深层常量优化(这样的开销等于把程序执行一遍),而使用constexpr,不仅指向了需要优化的代码块,还可以通过其本身的限制降低整体的优化复杂性。最重要的是,constexpr保证了优化发生的必然性,而常量优化具体做到何种程度则依赖具体的实现(尽管目前常量优化已经写入语言标准),因此引入constexpr无疑加强了程序员对代码的控制性。

猜你喜欢

转载自blog.csdn.net/FYZDMMCpp/article/details/82707642
今日推荐