constexpr
和有什么区别const
?
- 我什么时候可以只使用其中之一?
- 我什么时候可以同时使用,我应该如何选择一个?
这两个关键字都可以在对象和函数的声明中使用。应用于对象时的基本区别是:
const
将对象声明为常量。这意味着保证一旦初始化,该对象的值就不会改变,并且编译器可以利用这一事实进行优化。它还有助于防止程序员编写修改初始化后不打算修改的对象的代码。
constexpr
声明一个对象适合在标准所称的常量表达式中使用。但请注意,这constexpr
不是唯一的方法。
当应用于函数时,基本区别是:
const
只能用于非静态成员函数,一般不能用于函数。它保证成员函数不会修改任何非静态数据成员(可变数据成员除外,无论如何都可以修改)。
constexpr
可以与成员函数和非成员函数以及构造函数一起使用。它声明函数适合在常量表达式中使用。编译器只会在函数满足特定标准 (7.1.5/3,4) 时接受它,最重要的是(†):
return
允许使用一条语句。在构造函数的情况下,只允许使用初始化列表、typedef 和静态断言。(不过= default
,= delete
也被允许。)asm
声明、goto
语句、带有除case
and以外标签的语句default
、try-block、非文字变量的定义类型,静态或线程存储持续时间的变量的定义,不执行初始化的变量的定义。如上所述,constexpr
声明对象和函数都适合在常量表达式中使用。常量表达式不仅仅是常量:
它可以用于需要编译时评估的地方,例如,模板参数和数组大小说明符:
template<int N>
class fixed_size_list
{ /*...*/ };
fixed_size_list<X> mylist; // X must be an integer constant expression
int numbers[X]; // X must be an integer constant expression
但请注意:
将某些东西声明为constexpr
不一定保证它将在编译时进行评估。它可以用于此类,但也可以用于在运行时评估的其他地方。
一个对象可以在没有被声明的情况下适用于常量表达式constexpr
。例子:
int main()
{
const int N = 3;
int numbers[N] = {1, 2, 3}; // N is constant expression
}
这是可能的N
,因为 是常量并在声明时用文字初始化,满足常量表达式的条件,即使它没有被声明constexpr
。
那么我什么时候必须使用constexpr
呢?
N
可以用作常量表达式而无需声明constexpr
。这适用于所有对象:const
[这是由于第 5.19/2 节:常量表达式不得包含涉及“左值到右值修改,除非 [...] 整数或枚举类型的左值 [...]”的子表达式,感谢 Richard Smith 纠正我的早先声称这适用于所有文字类型。]
对于一个适合在常量表达式中使用的函数,它必须被显式声明constexpr
;仅仅满足常量表达式函数的标准是不够的。例子:
template<int N>
class list
{ };
constexpr int sqr1(int arg)
{ return arg * arg; }
int sqr2(int arg)
{ return arg * arg; }
int main()
{
const int X = 2;
list<sqr1(X)> mylist1; // OK: sqr1 is constexpr
list<sqr2(X)> mylist2; // wrong: sqr2 is not constexpr
}
const
我什么时候可以/应该同时使用两者constexpr
?
A. 在对象声明中。当两个关键字都引用要声明的同一个对象时,这从来没有必要。constexpr
暗示const
。
constexpr const int N = 5;
是相同的
constexpr int N = 5;
但是,请注意,在某些情况下,每个关键字都引用声明的不同部分:
static constexpr int N = 3;
int main()
{
constexpr const int *NP = &N;
}
在这里,NP
被声明为地址常量表达式,即本身就是常量表达式的指针。(当通过将地址运算符应用于静态/全局常量表达式来生成地址时,这是可能的。)这里,两者constexpr
和const
都是必需的:constexpr
总是指正在声明的表达式(这里NP
),而const
指int
(它声明了一个指针-到常量)。删除const
会使表达式非法(因为 (a) 指向非常量对象的指针不能是常量表达式,并且 (b)&N
实际上是指向常量的指针)。
B. 在成员函数声明中。在 C++11 中,constexpr
意味着const
,而在 C++14 和 C++17 中则不是这样。在 C++11 下声明为的成员函数
constexpr void f();
需要声明为
constexpr void f() const;
在 C++14 下,以便仍然可以用作const
函数。
const
适用于variables,并防止它们在您的代码中被修改。
constexpr
告诉编译器这个表达式会产生一个编译时常量 value,所以它可以用在数组长度、分配给const
变量等地方。Oli 给出的链接有很多很好的例子。
基本上,它们完全是 2 个不同的概念,可以(并且应该)一起使用。
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 和静态断言。它的参数和返回值必须具有字面量类型。它可以与非常量表达式参数一起使用,但是当这样做时,结果不是常量表达式。
常量表达式函数旨在在不牺牲性能或类型安全的情况下替换宏和硬编码文字。
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
。它必须使用常量表达式或由带有常量表达式参数的常量表达式构造函数构造的右值来初始化。
常量表达式对象的行为就像它被声明一样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 和静态断言。它的参数必须有文字类型。
常量表达式构造函数允许编译器在编译时初始化对象,前提是构造函数的参数都是常量表达式。
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的《 Effective Modern C++》一书中的提示constexpr
:
constexpr
对象是 const 并使用编译期间已知的值进行初始化;constexpr
当使用编译期间已知的参数调用函数时,函数会产生编译时结果;constexpr
对象和函数可以在比非constexpr
对象和函数更广泛的上下文中使用;constexpr
是对象或函数接口的一部分。两者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
量,也可以用来指定数组大小和像这样的运行时常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.
}
};
Aconstexpr
是一个不同的概念。如果将编译时常量作为参数传递,它将函数(成员或非成员)标记为可以在编译时评估的函数。例如你可以这样写。
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.
顺便说一下,这些constexpr
函数是常规的 C++ 函数,即使传递了非常量参数也可以调用。但在这种情况下,您将获得非 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)];
}
};
一个更“疯狂”的样本。
class test3
{
public:
int value;
// constexpr const method - can't chanage the values of object fields and can be evaluated at compile time.
constexpr int getvalue() const
{
return(value);
}
constexpr test3(int Value)
: value(Value)
{
}
};
constexpr test3 x(100); // OK. Constructor is constexpr.
int array[x.getvalue()]; // OK. x.getvalue() is constexpr and can be evaluated at compile time.
根据 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,函数必须相当简单:只是一个计算值的返回语句。constexpr 函数可用于非常量参数,但完成后结果不是常量表达式。我们允许在不需要常量表达式的上下文中使用非常量表达式参数调用 constexpr 函数,这样我们就不必定义本质上相同的函数两次:一次用于常量表达式,一次用于变量。
在一些地方,语言规则需要常量表达式(例如,数组边界(第 2.2.5 节、第 7.3 节)、案例标签(第 2.2.4 节、第 9.4.2 节)、一些模板参数(第 25.2 节)和使用 constexpr 声明的常量)。在其他情况下,编译时评估对性能很重要。与性能问题无关,不变性(具有不可更改状态的对象)的概念是一个重要的设计问题(第 10.4 节)。
Aconst int var
可以在运行时动态设置为一个值,一旦设置为该值,就不能再更改它。
Aconstexpr 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
}
上面的代码片段编译得很好,我已经注释掉了那些导致它出错的代码。
这里需要注意的关键概念是compile time
和的概念run time
。C++ 中引入了新的创新,旨在尽可能多地** know **
在编译时进行某些事情,以提高运行时的性能。
任何不涉及上述两个关键概念的解释尝试都是幻觉。
正如@0x499602d2 已经指出的那样,const
仅确保在初始化后不能更改值,而 as 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
我认为任何答案都没有真正说明它有什么副作用,或者实际上是什么。
constexpr
和const
at namespace/file-scope 在使用文字或表达式初始化时是相同的;但是对于函数,const
可以由任何函数constexpr
初始化,但由非 constexpr(未用 constexpr 或非 constexpr 表达式标记的函数)初始化会产生编译器错误。两者constexpr
和const
都是变量的隐式内部链接(实际上,如果编译 -O1 或更强,它们将无法进入链接阶段,并且static
不会强制编译器为const
或constexpr
在时发出内部(本地)链接器符号-O1 或更强;唯一这样做的时候是您获取变量的地址。除非用ie表示const
,否则它将是一个内部符号constexpr
extern
extern constexpr/const int i = 3;
需要使用)。在函数上,constexpr
使函数永远不会到达链接阶段(无论定义extern
或inline
-O0 或 -Ofast 是什么),而const
永远不会,并且static
仅对-O1inline
及更高版本有此影响。当const
/constexpr
变量由constexpr
函数初始化时,负载总是使用任何优化标志进行优化,但如果函数只是static
or inline
,或者变量不是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
on a function 还可以防止在函数中使用在编译时无法完成的任何事情;例如,在 上呼叫<<
操作员std::cout
。
constexpr
在块范围内的行为相同,如果由非 constexpr 函数初始化,则会产生错误;该值也立即被替换。
最后,它的主要用途就像 C 的内联函数,但它仅在该函数用于初始化文件范围变量时才有效(这些函数不能在 C 上执行,但它们可以在 C++ 上执行,因为它允许动态初始化文件-范围变量),除了该函数也不能将全局/局部符号导出到链接器,即使使用extern/static
,您可以inline
在 C 上使用它;块范围变量赋值函数可以简单地使用 -O1 优化进行内联,而无需constexpr
使用 C 和 C++。
在 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 types”。
如果我们使用 static 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
};
首先,两者都是 c++ 中的限定符。声明为 const 的变量必须初始化并且以后不能更改。因此,通常声明为 const 的变量甚至在编译之前就会有一个值。
但是,对于 constexpr 它有点不同。
对于 constexpr,您可以给出一个可以在程序编译期间计算的表达式。
显然,声明为 constexper 的变量以后不能像 const 一样更改。