目录
整数字面值即为常量表达式(constant expression),简单的算术表达式也是常量表达式,如20*4+8。整型常量自身可依照常量表达式进行初始化,还能利用前者组成新的常量表达式:
const int x = 2;
const int three_x = 3 * 2;
const int y = 6;
const int y3x = y + three_x;
常量表达式的功能
一些功能只能靠常量表达式实现:
1.设定数组界限:
int bounds = 99;
int array[bounds]; //错误
const int bounds2 = 4;
int array2[bounds2]; //正确
2.设定非类型模版参数(nontype template parameter):
template<unsigned size>
struct test
{};
test<bounds> ia; //错误,界限bounds不是常量表达式
test<bounds2> ia2; //正确,界限bounds是常量表达式
3.充当静态常量整型数据成员的初始化表达式:
class X
{
static const int the_answer = y3x;
};
4.对于能够进行静态初始化的内建型别和聚合体,可以将常量表达式作为其初始化表达式:
struct aggregrate
{
int a;
int b;
};
static aggregrate ma1 = { y3x , 245 }; //静态初始化,避免初始化先后次序问题,从而避免条件竞争
int dd = 256;
static aggregrate ma2 = { dd, dd }; //动态初始化
5.constexpr关键字的主要功能是充当函数限定符,声明为constexpr的函数可以在常量表达式中使用:
constexpr int square(int x)
{
return x * x;
}
int array[square(6)];
使用constexpr函数充当常量表达式要求其参数也为常量表达式,否则视为普通函数的调用,因此不能用来设定数组界限:
int dd = 20;
int array[square(dd)]; //错误,dd不是常量表达式
constexpr关键字和用户定义型别
除了上述例子所用的内建型别,只要满足要求并可充当字面值(代码中明确写出的值)的类型,就允许成为常量表达式,其必须满足的条件如下(关键字【平实】):
·必须具有平实拷贝构造函数
·必须具有平实析构函数
·非静态数据成员和基类都属于平实型别
·必须具备平实默认构造函数或常量表达式构造函数(后者不能进行拷贝/移动构造)
我们基于以下例子讨论:
class CX
{
private:
int a;
int b;
public:
CX() = default;
CX(int a_, int b_):a(a_),b(b_){}
int get_a()const
{
return a;
}
int get_b() const
{
return b;
}
int foo()const
{
return a + b;
}
};
上述例子问了保留默认构造函数,显式声明为默认,该型别符合字面型别的要求,能在常量表达式中使用,我们可以给出一些constexpr函数,负责创建该类型的新实例:
constexpr CX create_cx()
{
return CX();
}
//用于复制参数
constexpr CX clone(CX val)
{
return val;
}
C++14放宽了限制,只要不在constexpr函数内部改动非局部变量,就几乎可以进行任意操作,可以为成员函数和构造函数加上constexpr:
class CX
{
private:
int a;
int b;
public:
CX() = default;
constexpr CX(int a_, int b_):a(a_),b(b_){}
constexpr int get_a()const
{
return a;
}
constexpr int get_b() const
{
return b;
}
constexpr int combine()const
{
return a + b;
}
};
在C++11中,成员函数的const声明成了多余的修饰,因其限定作用为constexpr所包含,而C++14对该功能进行了扩充:
constexpr CX make_cx(int a)
{
return CX(a, 1);
}
constexpr CX half_double(CX old)
{
return CX(old.get_a()/2, old.get_b()*2);
}
constexpr int combine_squared(CX val)
{
return square(val.combine());
}
int array[combine_squared(half_double(make_cx(10)))];
上述例子意在说明,如果需要使用复杂的方式求得某些数组界限或整型常量,凭借constexpr完成任务将省去大量运算,常量表达式和constexpr函数带来的主要好处是:若依照常量表达式初始化字面值型别对象,会发生静态初始化,从而避免条件竞争和次序问题:
CX s = half_double(CX(11, 12));
若构造函数为constexpr函数,且参数为常量表达式,所属类会进行常量初始化(即constant initialization,常量往往在编译期就完成计算,在运行期直接套用算好的值,即静态初始化阶段),在并发编程中避免条件竞争。
constexpr对象
constexpr关键字还可以用在对象上,它会将对象声明为const常量。
主要用于分析和诊断:查验对象的初始化行为,核实其所依照的初值是常量表达式、constexpr构造函数,或由常量表达式构成的聚合体初始化表达式。
constexpr int i = 45;
constexpr std::string s("Hello"); //错误,std::string不是字面值型别
int foo();
constexpr int j = foo(); //错误,foo()并未声明为constexpr
constexpr函数
若要将某函数声明为constexper,其必须满足一定的条件,否则会产生编译错误:
·所有参数必须是字面值型别。
·返回值必须是字面值型别。
·函数只有一条return语句。
·return语句返回的表达式必须是常量表达式。
以上条件不难理解,因为constexpr函数必须能够嵌入到常量表达式中,而嵌入的结果仍然是常量表达式。C++14后放宽了部分要求:
·可以使用多条return语句。
·函数中创建的对象可以被修改。
·可以使用循环、条件分支和switch语句。
对于类所具有的constexpr成员函数,需要符合更多要求:
·constexpr成员不能是虚函数
·所述类必须是字面值型别
·构造函数必须初始化每一个基类
·构造函数必须初始化全体非静态数据成员
·成员初始化列表中的每个表达式都是常量表达式
·数据成员和基类调用构造函数进行初始化时,必须是constexpr构造函数。
constexpr模版
若函数模版或类模版加上constexpr修饰,而模版的某个特定的具现化中,其参数和返回值均不属于字面值型别,则constexpr关键字被忽略:
template<typename T>
constexpr T sum(T a, T b)
{
return a + b;
}
constexpr int i = sum(6, 9); //具备constexpr特性
std::string ss = sum(std::string("hello"), std::string(" world")); //不具备constexpr特性
具现化的函数模版必须满足前文的全部要求,才能成为constexpr函数。