constexpr和const之间有什么区别?
我什么时候只能使用其中一个?我什么时候可以同时使用这两种方法,我应该如何选择一种?
constexpr和const之间有什么区别?
我什么时候只能使用其中一个?我什么时候可以同时使用这两种方法,我应该如何选择一种?
当前回答
首先,两者都是c++中的限定符。声明常量的变量必须初始化,以后不能更改。因此,通常声明为常量的变量甚至在编译之前都会有一个值。
但是,对于constexpr来说,它有点不同。
对于constexpr,您可以给出一个表达式,该表达式可以在程序编译期间计算。
显然,声明为constexper的变量不能像const一样在将来更改。
其他回答
我认为任何答案都不能确切地说明它有什么副作用,或者确切地说,它是什么。
当用文字或表达式初始化时,名称空间/文件范围中的constexpr和const是相同的;但对于函数,const可以由任何函数初始化,但由非constexpr初始化的constexpr(未标记constexpr或非constexpr表达式的函数)将生成编译器错误。constexpr和const都是变量的隐式内部链接(实际上,如果编译-O1或更强,则它们无法生存到链接阶段,并且static不会强制编译器在-O1或更高时发出const或constexpr的内部(本地)链接器符号;它唯一做到这一点的时候是获取变量的地址。const和constexpr将是内部符号,除非用extern表示,即extern constexpr/const int i=3;需要使用)。在函数上,constexpr使函数永远不会到达链接阶段(无论是定义中的extern或inline,还是-O0或-Ofast),而const永远不会到达,static和inline只对-O1和更高版本有此影响。当const/constexpr变量由constexpr函数初始化时,加载总是使用任何优化标志进行优化,但如果函数仅为静态或内联,或者变量不是const/constexpr,则永远不会进行优化。
标准汇编(-O0)
#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
这清楚地表明,constexpr会导致const/constexpr文件范围变量在编译时初始化,并且不会产生全局符号,而不使用它会导致在运行时在main之前初始化。
使用-Ofast编译
甚至-Ofast没有优化负载!https://godbolt.org/z/r-mhif,所以需要constexpr
对于相同的结果,也可以从其他constexpr函数内部调用constexpr功能。函数上的constexpr还防止在函数中使用编译时无法执行的任何操作;例如,调用std::cout上的<<运算符。
块范围内的constexpr的行为相同,如果由非constexpr函数初始化,则会产生错误;该值也立即代入。
最后,它的主要目的类似于C的内联函数,但只有当该函数用于初始化文件范围变量时,它才有效(这些函数不能在C上执行,但可以在C++上执行,因为它允许动态初始化文件范围变元),除非该函数也不能将全局/局部符号导出到链接器,即使使用external/static,您可以在C上使用内联;块范围变量赋值函数可以简单地使用-O1优化进行内联,而无需在C和C++上使用constexpr。
首先,两者都是c++中的限定符。声明常量的变量必须初始化,以后不能更改。因此,通常声明为常量的变量甚至在编译之前都会有一个值。
但是,对于constexpr来说,它有点不同。
对于constexpr,您可以给出一个表达式,该表达式可以在程序编译期间计算。
显然,声明为constexper的变量不能像const一样在将来更改。
概述
const保证程序不会更改对象的值。但是,const不能保证对象经历哪种类型的初始化。考虑:const int mx=numeric_limits<int>::max();//OK:运行时初始化函数max()只返回一个文字值。然而,由于初始值设定项是函数调用,mx会进行运行时初始化。因此,不能将其用作常量表达式:整数arr[mx];//错误:“需要常量表达式”constexpr是一个新的C++11关键字,它使您无需创建宏和硬编码文字。它还保证在某些条件下,对象进行静态初始化。它控制表达式的求值时间。通过强制对其表达式进行编译时求值,constexpr允许您在依赖编译时常量的任何代码中定义对时间关键型应用程序、系统编程、模板以及一般来说至关重要的真正常量表达式。
常量表达式函数
常量表达式函数是声明为constexpr的函数。它的主体必须是非虚拟的,除了typedef和静态断言之外,它只能由一个返回语句组成。其参数和返回值必须具有文本类型。它可以与非常量表达式参数一起使用,但当这样做时,结果不是常量表达式。
常量表达式函数旨在替换宏和硬编码文本,而不牺牲性能或类型安全性。
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的对象。必须使用常量表达式或由带有常量表达式参数的常量表达式构造函数构造的右值来初始化它。
常量表达式对象的行为就像声明为常量一样,除了它需要在使用之前进行初始化,并且其初始值设定项必须是常量表达式。因此,常量表达式对象始终可以用作另一个常量表达式的一部分。
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和静态断言之外,它的主体必须为空。其参数必须具有文字类型。
常量表达式构造函数允许编译器在编译时初始化对象,前提是构造函数的参数都是常量表达式。
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的《有效的现代C++》一书中关于constexpr的提示:
constexpr对象是常量,并用编译期间已知的值初始化;constexpr函数在使用编译期间已知值的参数调用时产生编译时结果;constexpr对象和函数可以在比非constexpr的对象和函数更广泛的上下文中使用;constexpr是对象或函数接口的一部分。
资料来源:在C++中使用constexpr提高安全性、性能和封装。
const适用于变量,并防止在代码中修改它们。
constexpr告诉编译器,此表达式会产生编译时常量值,因此可以在数组长度、赋值给常量变量等位置使用。Oli给出的链接有很多很好的示例。
基本上,它们是两个不同的概念,可以(也应该)一起使用。
const int var可以在运行时动态设置为一个值,一旦设置为该值,就不能再更改。
constexpr int变量不能在运行时动态设置,而是在编译时动态设置。一旦设置为该值,就不能再进行更改。
下面是一个可靠的例子:
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
}
上面的代码段编译得很好,我已经注释掉了导致它出错的代码段。
这里需要注意的关键概念是编译时和运行时的概念。C++中引入了新的创新,旨在尽可能在编译时**了解**某些事情,以提高运行时的性能。
任何不涉及上述两个关键概念的解释都是幻觉。