条款15.只要有可能使用constexpr,就使用它

只要有可能使用constexpr,就使用它

constexpr表明的值不仅仅是常量,还是编译期可知的。这个表述并不全面,因为当constexpr被用于函数的时候,事情就有一些差别了。

constexpr对象和const对象一样,它们是编译期可知的

编译期可知的值,可能被存放到只读存储空间中。编译期可知的整数常量会出现在需要“整型常量表达式”的语境中,这类语境包括数组大小,整数模板参数(包括std::array对象的长度),枚举量,对齐修饰符等等。如果想在这些语境中使用变量,一定要把它们声明为constexpr,因为编译器会确保它们是编译期可知的

int sz;		//非constexpr变量

constexpr auto arraySize1 = sz;	//错误!sz的值在
								//编译器不可知
std::array<int, sz> data1;		//错误
constexpr auto arraySize2 = 10;	//没问题,10是编译器可知常量
std::array<int, arraySize2> data2;	//没问题

注意,const不提供constexpr所能保证的事,因为const对象不需要在编译器初始化它的值

int sz;
const auto arraySize = sz;	
std::array<int, arraySize> data;	//错误,arraySize值在编译器不可知

简而言之,所有constexpr对象都是const,但不是所有const都是constexpr

对于函数来说,如果实参是编译期常量,它们将产出编译期值。如果是运行时值,它们就将产出运行时值。

  • constexpr函数可以用于需求编译期常量的上下文。如果你传给constexpr函数的实参在编译期可知,那么结果将在编译期计算,如果实参的值在编译期不知道,你的代码就会被拒绝
  • 当一个constexpr函数被一个或者多个编译期不可知值调用时,它就像普通函数一样,运行时计算它的结果,这意味着你不需要两个函数,一个用于编译期计算,一个用于运行时计算。constexpr全做了
constexpr int pow(int base, int exp) noexcept
{
    ...
}

constexpr auto numConds = 5;
std::array<int, pow(3, numConds)> results;

pow前面的constexpr没有告诉我们pow返回一个const值,它只说了如果baseexp是编译期常量,pow返回值可能是编译期常量,如果baseexp不是编译期常量,pow结果将会在运行时计算。这意味着pow不止可以用于像std::array的大小这种需要编译期常量的地方,它也可以用于运行时环境。

auto base = readFromDB("base");	//运行时获取
auto exp = readFromDB("exponent");
auto baseToExp = pow(base, exp);		//运行时调用pow

因为constexpr函数必须能在编译期值调用的时候返回编译期结果,就必须对它的实现施加一些限制。

C++11中,constexpr函数的代码不超过一行语句,一个return

有两个技巧可以扩展constexpr函数的表达能力。第一,使用三元运算符?:来代替if-else语句,第二,使用递归代替循环

因此,pow函数可以这样实现

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

在C++14中,constexpr函数的限制变得非常宽松。

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

constexpr函数限制为只能获取和返回字面值类型,这基本上意味着具有那些类型的值能在编译期决定。在C++11中,所有的内建类型,除了void,都符合这个条件,但是用户自定义类型同样可能也是字面类型,因为构造函数和其他成员函数可以是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的构造函数被声明为constexpr,因为如果传入的参数在编译期可知,Point的数据成员也能在编译期可知。因此Point就能被初始化为constexpr

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

类似的,访问器xValueyValue也可以声明为constexpr,原因在于,若它们是通过一个在编译期已知其值的Point对象,即一个constexpr Point对象来调用的话,数据成员x和y的值就可以在编译期获知。这使得我们可以写一个constexpr函数里面调用Point的访问器并初始化constexpr对象。

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);

这意味着mid对象,尽管在其初始化过程中涉及了构造函数,访问器,还有非成员函数的调用,却仍可以在只读内存中得以创建。这意味着,你可以将一个类似mid.xValue()*10的表达式运用到模板形参中,或是指定枚举量的表达式中。这也意味着以前相对严格的某一行代码只能用于编译期,某一行代码只能用于运行时的界限变得模糊,一些运行时的普通计算能并入编译时。越多这样的代码并入,你的程序就越快。

在C++11中,有两个限制使得Point的成员函数setXsetY不能声明为constexpr。第一,它们修改了它们操作的对象的状态,并且在C++11中,constexpr成员函数是隐式的const,第二,它们只能有void返回类型,void类型不是C++11中的字面值类型。这两个限制在C++14中放开了,所以C++14中Pointset函数也能声明为constexpr

class Point{
public:
    ...
    constexpr void setX(double newX) noexcept { x = newX; }
    constexpr void setY(double newY) noexcept { y = newY; }
    ...
};

现在也可以写这样的函数

constexpr Point reflection(const Point& p) noexcept
{
    Point result;
    result.setX(-p.xValue());
    result.setY(-p.yValue());
    return result;
}

客户端代码可以这样写

constexpr Point p1(1,1);
constexpr Point p2(2,2);
constexpr auto mid = midpoint(p1, p2);

constexpr auto reflectedMid = reflection(mid);		

要点速记

  • constexpr对象是const,它的值在编译期可知
  • 当传递编译期可知的值时,constexpr函数可以产生编译期结果
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值