C++11 constexpr 关键字用法

constexpr 是 C++11 中增加的关键字,我们可以使用该关键字定义以下对象:

  1. 编译期常量;
  2. 常量表达式函数;
  3. 编译期常量对象。

以下代码运行环境为:win10 专业版 + vs2019 社区版。

 

1. 编译期常量

常量分为编译期常量、运行期常量。

编译期常量指的是在编译阶段就可以确定其值,并将其结果展开到使用的地方,不占用内存空间。

运行期常量本质上是只读的变量,需要占用内存空间,编译时无法确定其值,运行时,无法修改其值。

constexpr 关键字就是用来定义编译期的常量。

有些同学可能想,constexpr 和 const 有什么区别呢?

constexpr 只能定义编译期常量,而 const 可以定义编译期常量,也可以定义运行期常量。

请看下面 const 的代码:

void test()
{
    const int a = 10;

    int b = 20;
    const int c = b;
}

第 3 行定义一个编译期常量。

第 6 行定义了一个运行期常量。这是因为 b 是一个变量,编译期无法确定其值,必须到运行期才能确定。而 c 的值必须等到 b 确定才能确定,所以,c 是一个运行期常量。

上面第 6 行代码中的常量定义如果将 const 换成 constexpr ,则代码就会报语法错误,因为 constexpr 只能定义编译期常量。

void test()
{
    const int a = 10;

    int b = 20;
    constexpr int c = b;
}

上述代码编译报错如下:

说到常量有些同学可能不太清楚,程序中为什么要使用常量呢?有两点原因如下:

  1. 程序中总是需要一些不能修改的数据,写成变量则就有被意外修改的风险。
  2. 常量可以在编译期确定其值,某些场景下可以将一些计算任务放在编译阶段,从而提升程序效率。

 

2. 常量表达式函数

什么是常量表达式函数呢?请看下面的普通函数:

int my_sum(int n)
{
    if (n == 1)
    {
        return 1;
    }

    return n + my_sum(n - 1);
}

普通函数必须在运行时才能执行,进而计算出结果。而常量表达式函数要求函数在编译期就计算出结果,运行时直接使用结果。也就是说将函数的执行从运行阶段转移到编译阶段,提升程序运行效率。

将上面的 my_sum 函数定义为常量表达式函数非常简单,只需要在函数返回值类型前面加上 constexpr 关键字即可。示例代码如下:

constexpr int my_sum(int n)
{
    if (n == 1)
    {
        return 1;
    }

    return n + my_sum(n - 1);
}

为了能够让 my_sum 函数在编译阶段计算出结果,我们必须使用 constexpr 编译期常量来保存常量表达式函数的结果,否则 my_sum 函数仍然会在运行期执行。如下代码所示:

constexpr int my_sum(int n)
{
    if (n == 1)
    {
        return 1;
    }

    return n + my_sum(n - 1);
}

void test()
{
    constexpr int a = my_sum(3);
}

我们在 VS 2019 中查看第 13 行代码对生成的汇编代码如下:

mov   dword ptr [a], 6  

第 13 行代码只对应一行汇编指令,数字 6 则是编译阶段执行 my_sum(3) 的计算结果。

如果将 constexpr 去掉,则第 13 行对应的汇编代码如下:

push      3  
call      my_sum (03913B1h)  
add       esp,4  
mov       dword ptr [a],eax 

从汇编指令的条数来看,常量表达式函数明显较少。并且,运行期的函数计算每次都需要调用 my_sum, 而常量表达式则不是,运行效率更高。

 

3. 编译期常量对象

编译期常量对象的任何计算都在编译期完成。定义编译期常量对象,有以下几点要求:

  1. 构造函数使用 constexpr 修饰,必须使用初始化列表对成员进行初始化。
  2. 对象调用的成员函数必须使用 constexpr 修饰。请看下面的示例代码:
class Box
{
public:
    constexpr Box(int l, int w, int h) : m_l(l), m_w(w), m_h(h) {}
    constexpr int get_volume() const
    {
        return m_l * m_w * m_h;
    }

    int get_sum() const
    {
        return (m_l + m_w + m_h);
    }

public:
    int m_l;
    int m_w;
    int m_h;
};

void test()
{
    constexpr Box box(10, 20, 30);
    constexpr int volume = box.get_volume();
    int my_sum = box.get_sum();
}

第 4 行 Box 的构造函数使用 constexptr 修饰,并且使用初始化列表对成员进行初始化。这就保证了对象成员 m_l、m_w、m_h 在编译期确定其值。

第 5 行 get_volume 函数也被定义为了常量表达式成员函数,调用该函数则会在编译阶段计算出结果。

第 10 行 get_sum 函数则是普通函数,该函数必须声明为 const 函数,否则无法被常量对象调用。

第 23 行 Box 类型的对象 box 必须使用 constexpr 来修饰,使得其能够调用常量表达式构造函数。该行代码对应的汇编代码如下:

constexpr Box box(10, 20, 30);
mov         dword ptr [box], 0Ah  
mov         dword ptr [ebp-10h], 14h  
mov         dword ptr [ebp-0Ch], 1Eh  

其中 0Ah 为十六进制,对应十进制为 10,14h 对应十进制为 20, 1Eh 对应十进制为 30, 三行汇编代码的作用就是将 10、20、30 赋值给成员变量 m_l、m_w、m_h。注意:此处并没有调用 Box 构造函数。

第 24 行通过常量对象 box 调用常量表达式成员函数,使得在编译期计算出函数的执行结果,并赋值给编译期常量 volume。该行代码对应的汇编代码如下:

constexpr int volume = box.get_volume();
mov         dword ptr [volume], 1770h  

其中, 十六进制 1770h 对应的十进制为 6000,该行代码的作用是将值赋值 6000 给常量 volume。函数 get_volume 已经在编译期执行完毕,运行期直接使用计算结果 6000。

第 25 行通过常量对象调用了普通函数 show,该函数会在运行期进行执行。该行代码对应的汇编代码如下:

int my_sum = box.get_sum();
lea         ecx, [box]  
call        Box::get_sum (0FD1131h)  
mov      dword ptr [my_sum],eax  

call 指令表示调用后面的函数。我们可以看到,运行期调用了 get_sum 函数计算结果,而不是直接使用结果。

至此,关于 constexpt 讲解完毕,希望对你有所帮助!

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值