Item15 Use constexpr whenever possible

   如果有人问在C++11引入的众多关键字中,有哪个关键字是最让你迷惑的,我会立马回答是constexpr,当这个关键字应用到对象上的时候,它其实就是一个加强版的const,但是当它应用到函数身上,它就有了不同的含义。为此很有必要去深入学习constexpr解除其迷惑,使得我们在使用它的时候是符合预期的。

​   从概念上讲,constexpr修饰的值不仅仅是一个常量值,而且还是一个编译期(参见runtime-vs-compile-time)就知道的值,相比于const来说有几点区别,其一就是const修饰的变量可以是编译期的值,也可以是运行期的值。而constexpr必须是编译期的值,其二就是const只能用于修饰类的成员函数,而constexpr可以修饰普通的函数,并且两者的含义完全不同。首先让我们先来认识一下constexpr的最基本的功能。

int sz;
constexpr auto arraySize1 = sz;      // error sz's value not known at compilation
std::array<int, sz> data1           // error array size need compilation value
constexpr auto arraySize2 = 10;     // fine
std::array<int, arraySize2> data2;  // fine

​   constexpr修饰的变量一定要求是编译期的值,而const没有这个要求。通过这个事实我们可以知道是constexpr的变量一定是const,反之则不然。那么一切使用const的地方其实都可以替换为constexpr这也是本文的题目所要表达的含义。

​   我们都知道在C++中有些地方必须要传入编译期的值,比如数组的大小,˙整型模版参数(标准库的bitset容器),枚举值,对齐大小等等。在这些地方如果你想传入一个编译期的值,const是无法保证的,除非你确定你const修饰的变量都是编译期的值,倘若后面修改代码使得变成非编译期的值,将会导致大量编译错误,而这些地方使用constexpr是最万无一失的了,因为编译器会确保constexpr是编译期值。

​   最后我们来谈一下constexpr的一个与众不同的地方,就是修饰函数,和const完全不同,但是却隐式的包含了const的功能,这再次验证了,凡是出现const的地方其实都是可以替换为constexprconstexpr修饰的函数可以使得该函数可以在编译期运行,产生一个编译期的值,也可以像普通的函数一样在运行期执行,然后产生一个运行期的值。这很大程度上取决于传入的参数是编译期的值还是运行期的值。下面是一个例子:

constexpr  int pow(int base, int exp) noexcept
{
  return 10;
}
int base = 10;
constexpr constexpr_base data3 = 10;
std::array<int,pow(1,2)> data1;             // ok
std::array<int,pow(base,2) data2;           //compile error
std::array<int,pow(constexpr_base,2) data3; //ok

​   pow的实现在这里先暂时直接返回,然后当传入pow的参数是编译期值的时候是可以作为数组的长度的,否则会编译失败。接下来我们来实现一下pow

constexpr int pow (int base, int exp) noexcept
{
    auto result = 1;
    for(int i = 0;i < exp; ++i) result *= base;
    return result;
}

​ 很不幸,上面这个版本的pow并不work,会出现很多编译的错误如下:

pow.cc:7:3: error: statement not allowed in constexpr function
  for (int i = 0; i < exp; ++i) result *= base;

​   意思就是for这个语句无法在constexpr中使用,查询C++11相关的文档才知道constexpr只能包含一个return语句,这大大限制了constexpr的功能。好在C++11中不乏一些奇淫技巧,可以在return里面使用问号表达式或者单if语句。

constexpr int pow(inr base, int exp) noexcept
{
    return (exp == 0 ? 1 : base * pow(base, exp -1));    
}

​   除了上面这个限制外,还有另外一个限制就是只能返回LiteralType,简单来说就是其值可以在编译期知道的,内置类型中除了void都是LiteralType类型,自定义类型也可以是LiteralType类型,这取决于其自定义类型的构造函数是否是constexpr

class Point {
  public:
    constexpr Point(double xVal = 0, double yVal = 0) noexcept
      : x(xVal),y(yval)
    {}

    constexpr double xValue() const noexcept { return x; }
    constexpr double yValue() const noexcept { return y; }

    void setX(double newX) noexcept { x = newX; }
    void setY(double newY) noexcept { y = newY; }
  private:
    double x,y;
};

​   上面这个自定义类型其构造函数就是一个编译期的值,所以Point就是一个LiteralType类型,但是setXsetY却不是constexpr类型的,这是因为上文中说到的void类型,这不是一个LiteralType 类型,所以无法设置为constexpr类型。

constexpr Point p1(9.4, 27.7);
constexpr Point p2(28.8, 5.3);

constexpr Point midpoint(const Point& p1, const Point& p2) noexcept
{
    return { (p1.xValue() + p2.xValue()) / 2, (p1.yValue() + p2.yValue()) /2}    
}
constexpr auto mid = midpoint(p1,p2);

​   midpoint就是一个不折不扣的基于自定义类型的常量函数。一切就是如此的简单,对于上文中提到的void 不是LiteralType类型,常量函数中只能包含一个return语句等问题,都在C++14中得到了解决。总而言之尽可能的去使用constexpr代替const,这样会大大提高其适用范围。

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值