什么是constexpr?

系统很难分辨一个初始值是否为常量表达式,但用constexpr类型声明的变量可以由编译器来验证变量的值是否是一个常量表达式,使常量表达式在编译期就可以计算得到结果

什么是常量表达式呢?值不会改变且在编译时就可以得到计算结果的表达式。而非常量表达式只能在运行期计算出结果

为什么需要constexpr?

因为对于常量表达式可以在编译阶段计算出结果,极大地提高执行效率(表达式在编译阶段计算出了结果,就可以不用再每次运行时来计算,这肯定是节约了时间)

特性

  • 作用范围:可作用于变量、函数(包括模板函数)、class的构造函数
  • 将⼀个函数、变量标记为constexpr的同时,也将它标记为const。但相反并不成⽴,⼀个const的变量或函数,并不是 constexpr的
  • 为不能修改数据提供保障
  • 有些场景,编译器可以在编译期对constexpr的代码优化,提高效率
  • 相⽐宏来说,没有额外的开销,但更安全可靠
  • 即使一个常量表达式被constexpr修饰,拥有在编译阶段计算得出结果的能力,但这并不代表它一定就能在编译阶段被执行,是否执行由编译器说了算

引用

constexpr所引用的对象必须在编译期就决定地址

#include <iostream>
using namespace std;

int g_tempA = 4;
const int g_conTempA = 4;
constexpr int g_conexprTempA = 4;

int main(void)
{
    int tempA = 4;
    const int conTempA = 4;
    constexpr int conexprTempA = 4;
    /*1.正常运行,编译通过*/
    const int &conptrA = tempA;
    const int &conptrB = conTempA;
    const int &conptrC = conexprTempA;

    /*2.有两个问题:一是引用到局部变量,不能再编译器确定;二是conexprPtrB和conexprPtrC应该为constexpr const类型,编译不过*/
    constexpr int &conexprPtrA = tempA;
    constexpr int &conexprPtrB = conTempA;
    constexpr int &conexprPtrC = conexprTempA;

    /*3.第一个编译通过,后两个不通过,原因是因为conexprPtrE和conexprPtrF应该为constexpr const类型*/
    constexpr int &conexprPtrD = g_tempA;
    constexpr int &conexprPtrE = g_conTempA;
    constexpr int &conexprPtrF = g_conexprTempA;

    /*4.正常运行,编译通过*/
    constexpr const int &conexprConPtrD = g_tempA;
    constexpr const int &conexprConPtrE = g_conTempA;
    constexpr const int &conexprConPtrF = g_conexprTempA;

    return 0;
}

指针

将constexpr作用于指针,会将指针设定为指针常量,而非常量指针,也就是说constexpr只对指针生效,和所指对象无关

const int* p = nullptr;     //p为常量指针
constexpr int* q = nullptr; //q为指针常量

constexpr指针不可以指向定义在函数体内的局部变量,因为这些变量并非存放在固定地址,进入函数作用域会初始化它们,离开函数作用域则销毁它们;相对的,对于那些定义在函数体之外的变量和局部静态对象,因为其生命周期是从初始化到程序终止,其地址并不会改变,所以constexpr指针可以指向他们

函数

constexpr函数当需要使用它的代码时,在编译期计算其返回值

规则:

  1. 只接受并返回文本类型,返回值不能是void
  2. 函数体不能声明变量或定义新的类型
  3. 函数的返回类型和形参类型都必须为字面值类型
  4. 可以是递归的
  5. 正文不能包含任何 goto 语句或 try

要想constexpr能在编译期通过,函数的返回类型和形参类型都必须为字面值类型,且函数中有且只有一条return语句,函数体内可以有其他语句,但只能包含在运行期不执行任何操作的,可以包含 using 指令、typedef 语句以及 static_assert 断言、空语句、null语句

constexpr函数可以多次定义,一般定义在头文件中

constexpr int func() { return 1; }
constexpr int a = func();   //a为常量表达式

//当我们为如下实例的func1传入为常量表达式的实参时,函数的返回值为常量表达式;若非常量表达式,则返回值也是非常量表达式
constexpr func1( size_t s ) { return a * s; }
int arr[func1(2)];  //对,func1(2)为常量表达式
int i = 1;
int arr[func1(i)];  //错,func1(i)不是常量表达式

class

构造函数不能为const,但字面值常量类的构造函数可以为constexpr

constexpr构造函数限制:

  1. 不能是虚函数
  2. 主体可以定义为 default 或 delete。否则必须既符合构造函数的要求不能包含return语句,又符合constexpr函数的要求,也就是说constexpr构造函数体一般为空
  3. 必须初始化所有数据成员,初始值或者使用constexpr构造函数,亦或是常量表达式
  4. 满足constexpr函数规则
#include <iostream>
using namespace std;

class Test 
{
public:
    constexpr Test(int num1, int num2) : m_num1(num1), m_num2(num2)
    {

    }

public:
    int m_num1;
    int m_num2;
};

int main(void) 
{
    constexpr Test t1(1, 2);

    enum e
    { 
        x = t1.m_num1, 
        y = t1.m_num2 
    };

    return 0;
}

Reference

C++11关键字constexpr看这篇就够了


他们曾如此骄傲的活过,贯彻始终