constexpr
和const
之间有什么区别?
答案 0 :(得分:103)
const
适用于变量,而会阻止您在代码中修改 。
constexpr
告诉编译器此表达式会产生编译时常量值,因此可以在数组长度等位置使用,分配给{ {1}}变量等.Oli给出的link有很多很好的例子。
基本上它们完全是两个不同的概念,可以(也应该)一起使用。
答案 1 :(得分:56)
const
保证程序不会更改对象的值。但是,const
不保证对象经历哪种类型的初始化。
考虑:
const int mx = numeric_limits<int>::max(); // OK: runtime initialization
函数max()
只返回一个文字值。但是,因为初始化程序是函数调用,mx
会进行运行时初始化。因此,您不能将其用作常量表达式:
int arr[mx]; // error: “constant expression required”
constexpr
是一个新的C ++ 11关键字,它使您无需创建宏和硬编码文字。在某些条件下,它还保证对象经历静态初始化。它控制表达式的评估时间。通过强制对其表达式进行编译时评估,constexpr
允许您定义真正的常量表达式,这对于时间关键型应用程序,系统编程,模板和一般来说,在任何依赖于编译时常量的代码中。
常量表达式函数是一个声明为constexpr
的函数。它的主体必须是非虚拟的,除了typedef和静态断言之外,它只包含一个return语句。它的参数和返回值必须具有文字类型。它可以与非常量表达式参数一起使用,但是当完成时,结果不是常量表达式。
常量表达式函数用于替换宏和硬编码文字,而不会牺牲性能或类型安全性。
constexpr int max() { return INT_MAX; } // OK
constexpr long long_max() { return 2147483647; } // OK
constexpr bool get_val()
{
bool res = false;
return res;
} // error: body is not just a return statement
constexpr int square(int x)
{ return x * x; } // OK: compile-time evaluation only if x is a constant expression
const int res = square(5); // OK: compile-time evaluation of square(5)
int y = getval();
int n = square(y); // OK: runtime evaluation of square(y)
常量表达式对象是一个声明为constexpr
的对象。必须使用常量表达式或由具有常量表达式参数的常量表达式构造函数构造的rvalue初始化它。
常量表达式对象的行为就像它被声明为const
一样,除了它在使用前需要初始化并且它的初始值设定项必须是常量表达式。因此,常量表达式对象始终可以用作另一个常量表达式的一部分。
struct S
{
constexpr int two(); // constant-expression function
private:
static constexpr int sz; // constant-expression object
};
constexpr int S::sz = 256;
enum DataPacket
{
Small = S::two(), // error: S::two() called before it was defined
Big = 1024
};
constexpr int S::two() { return sz*2; }
constexpr S s;
int arr[s.two()]; // OK: s.two() called after its definition
常量表达式构造函数是一个声明为constexpr
的构造函数。它可以有一个成员初始化列表,但除了typedef和static asserts之外,它的主体必须是空的。它的参数必须有文字类型。
常量表达式构造函数允许编译器在编译时初始化对象,前提是构造函数的参数都是常量表达式。
struct complex
{
// constant-expression constructor
constexpr complex(double r, double i) : re(r), im(i) { } // OK: empty body
// constant-expression functions
constexpr double real() { return re; }
constexpr double imag() { return im; }
private:
double re;
double im;
};
constexpr complex COMP(0.0, 1.0); // creates a literal complex
double x = 1.0;
constexpr complex cx1(x, 0); // error: x is not a constant expression
const complex cx2(x, 1); // OK: runtime initialization
constexpr double xx = COMP.real(); // OK: compile-time initialization
constexpr double imaglval = COMP.imag(); // OK: compile-time initialization
complex cx3(2, 4.6); // OK: runtime initialization
Scott Meyers关于constexpr
的 Effective Modern C ++ 一书的提示:
constexpr
个对象是const,并使用编译期间已知的值进行初始化; constexpr
函数在使用参数调用时产生编译时结果,这些参数的值在编译期间是已知的; constexpr
对象和函数可用于比非constexpr
对象和函数更广泛的上下文中; constexpr
是对象或函数接口的一部分。来源: Using constexpr to Improve Security, Performance and Encapsulation in C++
答案 2 :(得分:28)
根据Bjarne Stroustrup的“The C ++ Programming Language 4th Editon”一书
• const :意思是“我保证不会改变这个值”(§7.5)。这主要是用的
指定接口,以便可以将数据传递给函数,而不必担心它们被修改
编译器强制执行const的承诺
• constexpr :意思是“在编译时大致要评估”(第10.4节)。这主要用于指定常量,以允许
例如:
const int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression
constexpr double max2 = 1.4∗square(var); // error : var is not a constant expression
const double max3 = 1.4∗square(var); //OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument (§2.2.5)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time
constexpr double s2 = sum(v); // error : sum(v) not constant expression
对于可在常量表达式中使用的函数,即在将要计算的表达式中
编译器必须定义 constexpr 。
例如:
constexpr double square(double x) { return x∗x; }
要成为constexpr,函数必须相当简单:只需一个计算值的return语句。一个
constexpr函数可以用于非常量参数,但是当完成时,结果不是a
不断表达。我们允许使用非常量表达式参数调用constexpr函数
在不需要常量表达式的上下文中,因此我们根本没有定义
相同的函数两次:一次用于常量表达式,一次用于变量
在一些地方,语言规则需要常量表达式(例如,数组边界(§2.2.5,
§7.3),案例标签(§2.2.4,§9.4.2),一些模板参数(§25.2)和使用声明的常量
constexpr)。在其他情况下,编译时评估对性能很重要。独立于
性能问题,不可变性(具有不可改变状态的对象)的概念是一个
重要的设计问题(§10.4)。
答案 3 :(得分:20)
const
和constexpr
都可以应用于变量和函数。尽管它们彼此相似,但实际上它们是非常不同的概念。
const
和constexpr
都意味着它们的值在初始化后不能更改。例如:
const int x1=10;
constexpr int x2=10;
x1=20; // ERROR. Variable 'x1' can't be changed.
x2=20; // ERROR. Variable 'x2' can't be changed.
const
和constexpr
之间的主要区别是初始化值已知(评估)的时间。尽管const
变量的值可以在编译时和运行时同时求值,但是constexpr
总是在编译时求值。例如:
int temp=rand(); // temp is generated by the the random generator at runtime.
const int x1=10; // OK - known at compile time.
const int x2=temp; // OK - known only at runtime.
constexpr int x3=10; // OK - known at compile time.
constexpr int x4=temp; // ERROR. Compiler can't figure out the value of 'temp' variable at compile time so `constexpr` can't be applied here.
知道在编译时还是运行时知道该值的主要优点是,只要需要编译时间常数,就可以使用编译时间常数。例如,C ++不允许您使用可变长度指定C数组。
int temp=rand(); // temp is generated by the the random generator at runtime.
int array1[10]; // OK.
int array2[temp]; // ERROR.
所以这意味着:
const int size1=10; // OK - value known at compile time.
const int size2=temp; // OK - value known only at runtime.
constexpr int size3=10; // OK - value known at compile time.
int array3[size1]; // OK - size is known at compile time.
int array4[size2]; // ERROR - size is known only at runtime time.
int array5[size3]; // OK - size is known at compile time.
因此const
变量既可以定义编译时间常数(如size1
,也可以用于指定数组大小,又可以定义运行时常数如{{ 1}},仅在运行时才知道,不能用于定义数组大小。另一方面,size2
总是定义可以指定数组大小的编译时间常数。
constexpr
和const
都可以应用于函数。 constexpr
函数必须是成员函数(方法,运算符),其中应用const
关键字意味着该方法无法更改其成员(非静态)字段的值。例如。
const
class test
{
int x;
void function1()
{
x=100; // OK.
}
void function2() const
{
x=100; // ERROR. The const methods can't change the values of object fields.
}
};
是一个不同的概念。如果将编译时常量作为参数传递,则将一个函数(成员或非成员)标记为可以在编译时求值的函数。例如,您可以编写此代码。
constexpr
通过constexpr int func_constexpr(int X, int Y)
{
return(X*Y);
}
int func(int X, int Y)
{
return(X*Y);
}
int array1[func_constexpr(10,20)]; // OK - func_constexpr() can be evaluated at compile time.
int array2[func(10,20)]; // ERROR - func() is not a constexpr function.
int array3[func_constexpr(10,rand())]; // ERROR - even though func_constexpr() is the 'constexpr' function, the expression 'constexpr(10,rand())' can't be evaluated at compile time.
函数是常规C ++函数,即使传递了非恒定参数也可以调用它们。但是在这种情况下,您将获得非constexpr值。
constexpr
int value1=func_constexpr(10,rand()); // OK. value1 is non-constexpr value that is evaluated in runtime.
constexpr int value2=func_constexpr(10,rand()); // ERROR. value2 is constexpr and the expression func_constexpr(10,rand()) can't be evaluated at compile time.
也可以应用于成员函数(方法),运算符甚至构造函数。例如。
constexpr
更“疯狂”的样本。
class test2
{
static constexpr int function(int value)
{
return(value+1);
}
void f()
{
int x[function(10)];
}
};
答案 4 :(得分:7)
正如@ 0x499602d2已经指出的那样,const
只能确保在初始化之后无法更改值,因为constexpr
(在C ++ 11中引入)保证变量是编译时常量。登记/>
请考虑以下示例(来自LearnCpp.com):
cout << "Enter your age: ";
int age;
cin >> age;
const int myAge{age}; // works
constexpr int someAge{age}; // error: age can only be resolved at runtime
答案 5 :(得分:3)
const int var
可以在运行时动态设置为一个值,一旦将其设置为该值,就无法再对其进行更改。
constexpr int var
不能在运行时动态设置,而是在编译时动态设置。并且一旦将其设置为该值,就无法再更改。
这是一个可靠的例子:
int main(int argc, char*argv[]) {
const int p = argc;
// p = 69; // cannot change p because it is a const
// constexpr int q = argc; // cannot be, bcoz argc cannot be computed at compile time
constexpr int r = 2^3; // this works!
// r = 42; // same as const too, it cannot be changed
}
上面的代码片段编译良好,我已经注释掉了导致该错误的代码。
答案 6 :(得分:3)
我认为没有任何答案能够确切说明其副作用,甚至是副作用。
用文字或表达式初始化时,名称空间/文件作用域中的 constexpr
和const
是相同的;但是使用函数,const
可以由任何函数初始化,但是constexpr
由非constexpr初始化(未用constexpr或非constexpr表达式标记的函数)会生成编译器错误。 constexpr
和const
都是变量的隐式内部链接(实际上,如果编译-O1和更强的变量,它们将无法生存到链接阶段,并且static
不会强制{{1})处于-O1或更高时,编译器将为const
或constexpr
发出内部(本地)链接器符号;唯一这样做的情况是使用变量的地址。 }和const
将是一个内部符号,除非用constexpr
表示,即需要使用extern
。在函数上,extern constexpr/const int i = 3;
使函数永远不会到达链接阶段(无论定义中的constexpr
或extern
还是-O0或-Ofast),而inline
永远不会确实如此,并且const
和static
仅对-O1及更高版本具有此作用。通过inline
函数初始化const
/ constexpr
变量时,总是使用任何优化标志来优化负载,但是如果函数仅是{{1 }}或constexpr
,或者变量不是static
/ inline
。
标准编译(-O0)
const
编译为
constexpr
但是
#include<iostream>
constexpr int multiply (int x, int y)
{
return x * y;
}
extern const int val = multiply(10,10);
int main () {
std::cout << val;
}
编译为
val:
.long 100 //extra external definition supplied due to extern
main:
push rbp
mov rbp, rsp
mov esi, 100 //substituted in as an immediate
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov eax, 0
pop rbp
ret
__static_initialization_and_destruction_0(int, int):
.
.
.
这清楚地表明#include<iostream>
const int multiply (int x, int y)
{
return x * y;
}
const int val = multiply(10,10); //constexpr is an error
int main () {
std::cout << val;
}
导致multiply(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov eax, DWORD PTR [rbp-4]
imul eax, DWORD PTR [rbp-8]
pop rbp
ret
main:
push rbp
mov rbp, rsp
mov eax, DWORD PTR val[rip]
mov esi, eax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov eax, 0
pop rbp
ret
__static_initialization_and_destruction_0(int, int):
.
.
.
mov esi, 10
mov edi, 10
call multiply(int, int)
mov DWORD PTR val[rip], eax
文件范围变量的初始化在编译时发生,并且不产生全局符号,而没有使用它会导致初始化发生在{{1}之前}。
使用-Ofast编译
即使-Ofast也无法优化负载! https://godbolt.org/z/r-mhif,所以您需要 constexpr
const/constexpr
函数内部调用 main
函数以获得相同的结果。函数上的constexpr
还可防止使用函数在编译时无法完成的任何操作;例如,在constexpr
上调用constexpr
运算符。
constexpr
的行为相同,因为如果由非constexpr函数初始化,则会产生错误。该值也将立即替换。
最后,其主要目的类似于C的内联函数,但仅在该函数用于初始化文件作用域变量时才有效(该函数不能在C上执行,但可以在C ++上执行,因为它允许动态初始化文件范围变量),除了该函数也无法将全局/局部符号导出到链接器之外,即使使用<<
,也可以在C上使用std::cout
;只需使用-O1优化就可以内嵌大范围变量分配函数,而无需在C和C ++上使用constexpr
。
答案 7 :(得分:2)
在 C++ 中,如果使用常量表达式初始化 const 对象,我们可以在需要常量表达式的任何地方使用我们的 const 对象。
const int x = 10;
int a[x] = {0};
例如,我们可以在 switch 中做一个 case 语句。
constexpr 可用于数组。
constexpr 不是类型。
constexpr 关键字可以与 auto 关键字结合使用。
constexpr auto x = 10;
struct Data { // We can make a bit field element of struct.
int a:x;
};
如果我们用常量表达式初始化一个 const 对象,那么该 const 对象生成的表达式现在也是一个常量表达式。
常量表达式:可以在编译时计算其值的表达式。
x*5-4 // 这是一个常量表达式。 对于编译器来说,输入这个表达式和直接输入46没有区别。
初始化是强制性的。它只能用于阅读目的。它无法更改。到目前为止,“const”和“constexpr”关键字之间没有区别。
注意:我们可以在同一个声明中使用 constexpr 和 const。
constexpr const int* p;
通常,函数的返回值是在运行时获取的。 但是当满足某些条件时,对 constexpr 函数的调用将在编译时作为常量获得。
注意: 函数调用中发送给函数的参数变量的参数,如果有多个参数,则发送给所有参数变量,如果 CE 将在编译时计算函数的返回值时间。 !!!
constexpr int square (int a){
return a*a;
}
constexpr int a = 3;
constexpr int b = 5;
int arr[square(a*b+20)] = {0}; //This expression is equal to int arr[35] = {0};
为了使函数成为 constexpr 函数,函数的返回值类型和函数参数的类型必须在称为“文字类型”的类型类别中。
constexpr 函数是隐式内联函数。
不需要使用常量表达式调用 constexpr 函数。这不是强制性的。如果发生这种情况,计算将不会在编译时完成。它将被视为正常的函数调用。因此,在需要常量表达式的地方,我们将无法再使用该表达式。
1 ) 函数参数中使用的类型和函数返回值的类型必须是字面量类型。
2 ) 不应在函数内部使用具有静态生命周期的局部变量。
3 ) 如果函数合法,当我们在编译时用常量表达式调用这个函数时,编译器在编译时计算函数的返回值。
4 ) 编译器需要查看函数的代码,所以 constexpr 函数几乎总是在头文件中。
5 ) 为了使我们创建的函数成为 constexpr 函数,函数的定义必须在头文件中。因此,包含该头文件的源文件将看到该函数定义。
通常使用默认成员初始化,可以在类中初始化具有 const 和整型类型的静态数据成员。但是,为了做到这一点,必须同时存在“const”和“integral 类型”。
如果我们使用静态 constexpr,那么它不必是一个整数类型来在类中初始化它。只要我用常量表达式初始化就没有问题。
class Myclass {
const static int sx = 15; // OK
constexpr static int sy = 15; // OK
const static double sd = 1.5; // ERROR
constexpr static double sd = 1.5; // OK
};
答案 8 :(得分:0)
首先,两者都是c ++中的限定符。 声明为const的变量必须进行初始化,以后不能更改。 因此,通常声明为const的变量即使在编译之前也将具有值。
但是,对于constexpr来说有点不同。
对于constexpr,您可以给出一个可以在程序编译期间求值的表达式。
很明显,声明为constexper的变量将来不能像const一样进行更改。