首页 文章

`constexpr`和`const`之间的区别

提问于
浏览
427

constexprconst 之间有什么区别?

  • 我什么时候才能只使用其中一个?

  • 我何时可以同时使用两者?如何选择?

7 回答

  • 96

    基本含义和语法

    两个关键字都可以用于对象和函数的声明 . 应用于对象时的基本区别是:

    • const 将对象声明为常量 . 这意味着保证一旦初始化,该对象的值不会改变,并且编译器可以利用这个事实进行优化 . 它还有助于防止程序员编写修改初始化后无意修改的对象的代码 .

    • constexpr 声明一个对象适合在标准调用常量表达式中使用 . 但请注意 constexpr 并不是唯一的方法 .

    应用于函数时,基本区别在于:

    • const 只能用于非静态成员函数,而不能用于一般函数 . 它保证成员函数不会修改任何非静态数据成员 .

    • constexpr 可以与成员函数和非成员函数以及构造函数一起使用 . 它声明该函数适合在常量表达式中使用 . 如果函数满足某些标准(7.1.5 / 3,4),编译器将只接受它,最重要的是(†):

    • 函数体必须是非虚拟的并且非常简单:除了typedef和static asserts之外,只允许一个 return 语句 . 对于构造函数,只允许初始化列表,typedef和static assert . ( = default= delete 也是允许的 . )

    • 从C14开始,规则更加宽松,从那时起在constexpr函数中允许的内容: asm 声明, goto 语句,带有 casedefault 以外的标签的语句,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 .

    So when do I actually have to use constexpr?

    • object 如上面的 N 可用作常量表达式而不被声明为 constexpr . 对于以下所有对象都是如此:

    • const

    • 的整数或枚举类型和

    • 在声明时初始化,表达式本身就是一个常量表达式

    [这是由于§5.19/ 2:常量表达式不得包含涉及“左值到右值修改的子表达式,除非[...]积分或枚举类型的glvalue [...]”感谢Richard Smith纠正我的早先声称所有文字类型都适用 . ]

    • 如果 function 适合在常量表达式中使用,则 must 应显式声明为 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
    }
    

    When can I / should I use both, const and constexpr together?

    A. In object declarations. 当两个关键字都引用要声明的同一对象时,这是绝对必要的 . constexpr 暗示 const .

    constexpr const int N = 5;
    

    是相同的

    constexpr int N = 5;
    

    但请注意,可能存在关键字各自引用声明的不同部分的情况:

    static constexpr int N = 3;
    
    int main()
    {
      constexpr const int *NP = &N;
    }
    

    这里, NP 被声明为地址常量表达式,即指针本身是一个常量表达式 . (当通过将地址运算符应用于a来生成地址时,这是可能的static / global constant expression . )这里, constexprconst 都是必需的: constexpr 总是引用被声明的表达式(这里是 NP ),而 const 引用 int (它声明指向const的指针) . 删除 const 会使表达式变为非法(因为(a)指向非const对象的指针不能是常量表达式,而(b) &N 实际上是指向常量的指针) .

    B. In member function declarations. 在C 11中, constexpr 暗示 const ,而在C 14和C 17中则不是这种情况 . 在C 11下声明的成员函数

    constexpr void f();
    

    需要声明为

    constexpr void f() const;
    

    在C 14下,仍然可以用作 const 功能 .

  • 46

    const 适用于您的代码中的 variablesprevents them from being modified .

    constexpr 告诉编译器这个 expression 会产生 compile time constant value ,所以它可以在数组长度,分配给 const 变量等的地方使用.Oli给出的link有很多很好的例子 .

    基本上它们完全是两个不同的概念,并且可以(并且应该)一起使用 .

  • 1

    概述

    • const 保证程序 does not change an object’s value . 但是, const 不保证对象经历哪种类型的初始化 .

    考虑:

    const int mx = numeric_limits<int>::max();  // OK: runtime initialization
    

    函数 max() 仅返回文字值 . 但是,由于初始化程序是函数调用, mx 会进行运行时初始化 . 因此,您不能将其用作常量表达式:

    int arr[mx];  // error: “constant expression required”
    
    • constexpr 是一个新的C 11关键字,它使您无需创建宏和硬编码文字 . 在某些条件下,它还保证对象经历静态初始化 . 它控制表达式的评估时间 . 通过强制执行 compile-time evaluation of its expressionconstexpr 允许您定义真正的常量表达式,这些表达式对于时间关键型应用程序,系统编程,模板以及一般来说,依赖于编译时常量的任何代码都至关重要 .

    常量表达式函数

    常量表达式函数是声明为 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的书“Effective Modern C”中的提示 constexpr

    • constexpr 对象是const,并使用编译期间已知的值进行初始化;

    • constexpr 函数在使用参数调用时产生编译时结果,这些参数的值在编译期间是已知的;

    • constexpr 对象和函数可用于比非对象和函数更广泛的上下文中;

    • constexpr 是对象或函数接口的一部分 .

    资料来源:Using constexpr to Improve Security, Performance and Encapsulation in C++ .

  • 426

    根据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函数可以用于非常量参数,但是当完成时,结果不是常量表达式 . 我们允许在不需要常量表达式的上下文中使用非常量表达式参数调用constexpr函数,因此我们不能两次定义基本相同的函数:一次用于常量表达式,一次用于变量 .
    在少数地方,语言规则需要常量表达式(例如,数组边界(§2.2.5,§7.3),案例标签(§2.2.4,§9.4.2),一些模板参数(§25.2),以及使用constexpr声明的常量) . 在其他情况下,编译时评估对性能很重要 . 与性能问题无关,不可变性(具有不可更改状态的对象)的概念是一个重要的设计问题(第10.4节) .

  • 9

    constconstexpr 都可以应用于变量和函数 . 尽管它们彼此相似,但事实上它们是非常不同的概念 .

    constconstexpr 都表示在初始化后无法更改其值 . 例如:

    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.
    

    constconstexpr 之间的主要区别是它们的初始化值已知(已评估)的时间 . 虽然可以在编译时和运行时计算 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 变量可以定义 compile time constants ,如 size1 ,可用于指定数组大小, runtime constants ,如 size2 ,仅在运行时已知,不能用于定义数组大小 . 另一方面, constexpr 始终定义可以指定数组大小的编译时常量 .

    constconstexpr 也可以应用于函数 . 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 是一个不同的概念 . 它将函数(成员或非成员)标记为可在编译时 if compile time constants are passed as their arguments 评估的函数 . 例如,你可以写这个 .

    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.
    
  • 5

    正如@ 0x499602d2已经指出的那样, const 仅确保在初始化之后无法更改值,因为 constexpr (在C11中引入)保证变量是编译时常量 .
    请考虑以下示例(来自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
    
  • 26

    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
    }
    

    上面的代码片段编译得很好,我已经注释掉导致它出错的那些 .

相关问题