Sự khác biệt giữa `constexpr` và` const`


593

Sự khác biệt giữa constexprvà là constgì?

  • Khi tôi chỉ có thể sử dụng một trong số họ?
  • Khi nào tôi có thể sử dụng cả hai và làm thế nào tôi nên chọn một?

71
constexprtạo một hằng số thời gian biên dịch; constđơn giản có nghĩa là giá trị không thể thay đổi.
0x499602D2


Có thể bài viết này từ boost/hanathư viện có thể khai sáng một số constexprvấn đề mà bạn có thể sử dụng constexprvà nơi bạn không thể: boost.org/doc/libs/1_69_0/libs/hana/doc/html/iêu
Andry

@ 0x499602D2 " chỉ có nghĩa là giá trị mà không thể thay đổi " Đối với một vô hướng khởi tạo với một chữ, một giá trị không thể thay đổi cũng một hằng số thời gian biên dịch.
tò mò

@cquilguy Vâng nhận xét của tôi rất đơn giản. Phải thừa nhận rằng tôi cũng mới constexprtrở lại rồi :)
0x499602D2

Câu trả lời:


587

Ý nghĩa cơ bản và cú pháp

Cả hai từ khóa có thể được sử dụng trong khai báo các đối tượng cũng như các chức năng. Sự khác biệt cơ bản khi áp dụng cho các đối tượng là:

  • consttuyên bố một đối tượng là hằng số . Điều này ngụ ý một đảm bảo rằng, một khi được khởi tạo, giá trị của đối tượng đó sẽ không thay đổi và trình biên dịch có thể sử dụng thực tế này để tối ưu hóa. Nó cũng giúp ngăn lập trình viên viết mã sửa đổi các đối tượng không được sửa đổi sau khi khởi tạo.

  • constexprkhai báo một đối tượng là phù hợp để sử dụng trong những gì mà Standard gọi là biểu thức hằng . Nhưng lưu ý rằng đó constexprkhông phải là cách duy nhất để làm điều này.

Khi áp dụng cho các chức năng, sự khác biệt cơ bản là:

  • constchỉ có thể được sử dụng cho các hàm thành viên không tĩnh, không phải các hàm nói chung. Nó đảm bảo rằng hàm thành viên không sửa đổi bất kỳ thành viên dữ liệu không tĩnh nào.

  • constexprcó thể được sử dụng với cả các hàm thành viên và không phải thành viên, cũng như các hàm tạo. Nó khai báo hàm phù hợp để sử dụng trong các biểu thức không đổi . Trình biên dịch sẽ chỉ chấp nhận nó nếu hàm đáp ứng các tiêu chí nhất định (7.1.5 / 3,4), quan trọng nhất là () :

    • Phần thân hàm phải không ảo và cực kỳ đơn giản: Ngoài các typedefs và các xác nhận tĩnh, chỉ returncho phép một câu lệnh duy nhất . Trong trường hợp của hàm tạo, chỉ cho phép một danh sách khởi tạo, typedefs và xác nhận tĩnh được cho phép. ( = default= deletecũng được cho phép, mặc dù vậy.)
    • Kể từ C ++ 14, các quy tắc được nới lỏng hơn, những gì được phép kể từ đó bên trong một hàm constexpr: asmkhai báo, một gotocâu lệnh, một câu lệnh có nhãn khác với casedefault, khối thử, định nghĩa của một biến không thuộc loại chữ, định nghĩa của một biến thời gian lưu trữ tĩnh hoặc luồng, định nghĩa của một biến mà không có khởi tạo nào được thực hiện.
    • Các đối số và kiểu trả về phải là kiểu chữ (nghĩa là nói chung, các kiểu rất đơn giản, điển hình là vô hướng hoặc tổng hợp)

Biểu thức không đổi

Như đã nói ở trên, constexprkhai báo cả hai đối tượng cũng như các hàm phù hợp để sử dụng trong các biểu thức không đổi. Một biểu thức hằng không chỉ đơn thuần là hằng số:

  • Nó có thể được sử dụng ở những nơi yêu cầu đánh giá thời gian biên dịch, ví dụ, tham số mẫu và chỉ định kích thước mảng:

    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
  • Nhưng lưu ý:

    • Tuyên bố một cái gì đó constexprkhông nhất thiết đảm bảo rằng nó sẽ được đánh giá tại thời điểm biên dịch. Nó có thể được sử dụng cho như vậy, nhưng nó cũng có thể được sử dụng ở những nơi khác được đánh giá vào thời gian chạy.

    • Một đối tượng thể phù hợp để sử dụng trong các biểu thức không đổi mà không được khai báo constexpr. Thí dụ:

      int main()
      {
        const int N = 3;
        int numbers[N] = {1, 2, 3};  // N is constant expression
      }

    Điều này là có thể bởi vì N, là hằng số và được khởi tạo tại thời điểm khai báo bằng chữ, đáp ứng các tiêu chí cho biểu thức hằng, ngay cả khi nó không được khai báo constexpr.

Vậy khi nào tôi thực sự phải sử dụng constexpr?

  • Một đối tượng như Ntrên có thể được sử dụng như biểu thức không đổi mà không được khai báo constexpr. Điều này đúng với tất cả các đối tượng:

    • const
    • loại tích phân hoặc liệt kê
    • khởi tạo tại thời điểm khai báo với một biểu thức chính nó là một biểu thức không đổi

    . trước đó tuyên bố rằng điều này đúng với tất cả các loại nghĩa đen.]

  • Để một hàm phù hợp để sử dụng trong các biểu thức hằng, nó phải được khai báo rõ ràng constexpr; nó không đủ cho nó chỉ để đáp ứng các tiêu chí cho các hàm biểu thức không đổi. Thí dụ:

    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
    }

Khi nào tôi có thể / tôi nên sử dụng cả hai constconstexpr cùng nhau?

A. Trong khai báo đối tượng. Điều này không bao giờ cần thiết khi cả hai từ khóa đề cập đến cùng một đối tượng được khai báo. constexprngụ ý const.

constexpr const int N = 5;

giống như

constexpr int N = 5;

Tuy nhiên, lưu ý rằng có thể có các tình huống khi mỗi từ khóa đề cập đến các phần khác nhau của khai báo:

static constexpr int N = 3;

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

Ở đây, NPđược khai báo là một biểu thức hằng địa chỉ, tức là một con trỏ tự nó là một biểu thức hằng. (Điều này có thể xảy ra khi địa chỉ được tạo bằng cách áp dụng toán tử địa chỉ cho biểu thức hằng số tĩnh / toàn cục.) Ở đây, cả hai constexprconstđược yêu cầu: constexprluôn đề cập đến biểu thức được khai báo (ở đây NP), trong khi constđề cập đến int(nó khai báo một con trỏ- to-const). Việc xóa constbiểu thức sẽ khiến biểu thức không hợp lệ (vì (a) một con trỏ tới một đối tượng không phải là hằng số có thể là một biểu thức không đổi và (b) &Ntrên thực tế là một con trỏ không đổi).

B. Trong khai báo hàm thành viên. Trong C ++ 11, constexprngụ ý const, trong khi ở C ++ 14 và C ++ 17 thì không như vậy. Một hàm thành viên được khai báo theo C ++ 11 là

constexpr void f();

cần phải được tuyên bố là

constexpr void f() const;

trong C ++ 14 để vẫn có thể sử dụng như một consthàm.


3
IMO "không nhất thiết phải được đánh giá tại thời gian biên dịch" ít hữu ích hơn so với việc nghĩ chúng là "được đánh giá tại thời gian biên dịch". Các ràng buộc trên một biểu thức hằng có nghĩa là trình biên dịch sẽ tương đối dễ dàng để đánh giá nó. Một trình biên dịch phải khiếu nại nếu những ràng buộc đó không được thỏa mãn. Vì không có tác dụng phụ, bạn không bao giờ có thể nhận ra sự khác biệt cho dù trình biên dịch có "đánh giá" nó hay không.
aschepler

10
@aschepler Chắc chắn rồi. Quan điểm chính của tôi là nếu bạn gọi một constexprhàm trên biểu thức không hằng, ví dụ một biến thông thường, thì điều này là hoàn toàn hợp pháp và hàm này sẽ được sử dụng như bất kỳ hàm nào khác. Nó sẽ không được đánh giá tại thời điểm biên dịch (vì không thể). Có lẽ bạn nghĩ đó là điều hiển nhiên - nhưng nếu tôi tuyên bố rằng một hàm được khai báo constexprsẽ luôn được đánh giá vào thời gian biên dịch, thì nó có thể bị hiểu sai.
jogojapan

5
Vâng, tôi đã nói về constexprcác đối tượng, không phải chức năng. Tôi thích nghĩ constexprvề các đối tượng như buộc đánh giá thời gian biên dịch các giá trị và constexprtrên các hàm như cho phép hàm được đánh giá tại thời gian biên dịch hoặc thời gian chạy khi thích hợp.
aschepler

2
Một sự điều chỉnh: 'const' chỉ là một hạn chế mà BẠN không thể thay đổi giá trị của một biến; nó không đưa ra bất kỳ lời hứa nào rằng giá trị sẽ không thay đổi (nghĩa là bởi người khác). Đó là một tài sản ghi, không phải là một tài sản đọc.
Jared Grubb

3
Câu này: Nó đảm bảo rằng hàm thành viên không sửa đổi bất kỳ thành viên dữ liệu không tĩnh nào. bỏ lỡ một chi tiết quan trọng Các thành viên được đánh dấu là mutablecũng có thể được sửa đổi bởi các constchức năng thành viên.
Omnifarious

119

constáp dụng cho các biếnngăn không cho chúng bị sửa đổi trong mã của bạn.

constexprcho trình biên dịch biết rằng biểu thức này dẫn đến giá trị hằng số thời gian biên dịch , do đó nó có thể được sử dụng ở những nơi như độ dài mảng, gán cho constbiến, v.v ... Liên kết được đưa ra bởi Oli có rất nhiều ví dụ tuyệt vời.

Về cơ bản chúng là 2 khái niệm khác nhau hoàn toàn, và có thể (và nên) được sử dụng cùng nhau.


2
sử dụng const & constexpr, ví dụ: en.cppreference.com/w/cpp/container/array/get
Manohar Reddy Poreddy

64

Tổng quat

  • constđảm bảo rằng chương trình không thay đổi giá trị của đối tượng . Tuy nhiên, constkhông đảm bảo loại khởi tạo nào mà đối tượng trải qua.

    Xem xét:

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

    Hàm max()chỉ trả về một giá trị theo nghĩa đen. Tuy nhiên, vì trình khởi tạo là một lệnh gọi hàm, mxtrải qua quá trình khởi tạo thời gian chạy. Do đó, bạn không thể sử dụng nó như một biểu thức không đổi :

    int arr[mx];  // error: “constant expression required”
  • constexprlà một từ khóa C ++ 11 mới hỗ trợ bạn về nhu cầu tạo macro và mã hóa cứng. Nó cũng đảm bảo, trong những điều kiện nhất định, các đối tượng trải qua quá trình khởi tạo tĩnh . Nó kiểm soát thời gian đánh giá của một biểu thức. Bằng cách thực thi đánh giá thời gian biên dịch biểu thức của nó , constexprcho phép bạn xác định các biểu thức hằng thực sự rất quan trọng cho các ứng dụng quan trọng về thời gian, lập trình hệ thống, mẫu và nói chung, trong bất kỳ mã nào dựa trên hằng số thời gian biên dịch.

Hàm biểu thức hằng

Hàm biểu thức hằng là một hàm được khai báo constexpr. Phần thân của nó phải không ảo và chỉ bao gồm một câu lệnh return, ngoài các typedefs và các xác nhận tĩnh. Đối số và giá trị trả về của nó phải có kiểu chữ. Nó có thể được sử dụng với các đối số biểu thức không liên tục, nhưng khi điều đó được thực hiện thì kết quả không phải là biểu thức hằng.

Hàm biểu thức không đổi có nghĩa là để thay thế các macrochữ được mã hóa cứng mà không làm giảm hiệu suất hoặc loại an toàn.

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)

Các đối tượng biểu thức không đổi

Một đối tượng biểu thức không đổi là một đối tượng được khai báo constexpr. Nó phải được khởi tạo với một biểu thức không đổi hoặc một giá trị được xây dựng bởi một hàm tạo biểu thức hằng với các đối số biểu thức không đổi.

Một đối tượng biểu thức không đổi hoạt động như thể nó được khai báo const, ngoại trừ việc nó yêu cầu khởi tạo trước khi sử dụng và trình khởi tạo của nó phải là một biểu thức không đổi. Do đó, một đối tượng biểu thức hằng luôn có thể được sử dụng như một phần của biểu thức hằng khác.

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

Các hàm tạo biểu thức hằng

Một constructor biểu thức hằng là một constructor được khai báo constexpr. Nó có thể có một danh sách khởi tạo thành viên nhưng phần thân của nó phải trống, ngoài các typedefs và các xác nhận tĩnh. Đối số của nó phải có các loại nghĩa đen.

Hàm tạo biểu thức hằng cho phép trình biên dịch khởi tạo đối tượng tại thời gian biên dịch, với điều kiện là các đối số của hàm tạo đều là các biểu thức hằng.

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

Lời khuyên từ cuốn sách C ++ hiện đại hiệu quả của Scott Meyers về constexpr:

  • constexpr các đối tượng là const và được khởi tạo với các giá trị được biết trong quá trình biên dịch;
  • constexpr các hàm tạo ra kết quả thời gian biên dịch khi được gọi với các đối số có giá trị được biết trong quá trình biên dịch;
  • constexprcác đối tượng và chức năng có thể được sử dụng trong phạm vi bối cảnh rộng hơn so với các constexprđối tượng và chức năng;
  • constexpr là một phần của giao diện của đối tượng hoặc chức năng.

Nguồn: Sử dụng constexpr để cải thiện tính bảo mật, hiệu suất và đóng gói trong C ++ .


Cảm ơn mã ví dụ tuyệt vời cho thấy các tình huống khác nhau. Cũng tuyệt vời như một số giải thích khác, tôi thấy việc nhìn thấy mã trong hành động hữu ích và dễ hiểu hơn nhiều. Nó thực sự giúp củng cố sự hiểu biết của tôi về những gì đang xảy ra.
RTHarston

35

Theo cuốn sách "Biên tập ngôn ngữ lập trình C ++ thứ 4" của Bjarne Stroustrup
const : có nghĩa đại khái là '' Tôi hứa sẽ không thay đổi giá trị này '' (§7.5). Điều này được sử dụng chủ yếu để chỉ định các giao diện, để dữ liệu có thể được chuyển đến các chức năng mà không sợ nó bị sửa đổi.
Trình biên dịch thực thi lời hứa của const.
constexpr : có nghĩa là khoảng '' được đánh giá tại thời điểm biên dịch '' (§10.4). Điều này được sử dụng chủ yếu để chỉ định các hằng số, để cho phép
Ví dụ:

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.4square(var); // error : var is not a constant expression
const double max3 = 1.4square(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

Để một hàm có thể sử dụng được trong một biểu thức không đổi, nghĩa là, trong một biểu thức sẽ được trình biên dịch đánh giá, nó phải được định nghĩa constexpr .
Ví dụ:

constexpr double square(double x) { return xx; }


Để được constexpr, một hàm phải khá đơn giản: chỉ cần một câu lệnh trả về tính toán một giá trị. Hàm constexpr có thể được sử dụng cho các đối số không liên tục, nhưng khi hoàn thành, kết quả không phải là biểu thức hằng. Chúng tôi cho phép một hàm constexpr được gọi với các đối số biểu thức không liên tục trong các ngữ cảnh không yêu cầu biểu thức không đổi, do đó chúng tôi không định nghĩa về cơ bản cùng một hàm hai lần: một lần cho biểu thức không đổi và một lần cho biến.
Ở một vài nơi, các biểu thức hằng được yêu cầu bởi các quy tắc ngôn ngữ (ví dụ: giới hạn mảng (§2.2.5, §7.3), nhãn trường hợp (§2.2.4, §9.4.2), một số đối số mẫu (§25.2) và hằng được khai báo bằng constexpr). Trong các trường hợp khác, đánh giá thời gian biên dịch là quan trọng đối với hiệu suất. Không phụ thuộc vào các vấn đề hiệu suất, khái niệm bất biến (của một đối tượng có trạng thái không thể thay đổi) là một mối quan tâm thiết kế quan trọng (§10.4).


vẫn còn vấn đề hiệu suất. Có vẻ như hàm constexpr nếu được đánh giá trong thời gian chạy có thể chậm hơn phiên bản hàm không constexpr. Ngoài ra nếu chúng ta có một giá trị không đổi, chúng ta nên thích "const" hay "constexpr"? (thêm một câu hỏi kiểu được lắp ráp trông giống nhau)
CoffeDeveloper

31

Cả hai constconstexprcó thể được áp dụng cho các biến và chức năng. Mặc dù chúng giống nhau, nhưng thực tế chúng là những khái niệm rất khác nhau.

Cả hai constconstexprcó nghĩa là giá trị của chúng không thể thay đổi sau khi khởi tạo. Ví dụ:

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.

Sự khác biệt chính giữa constconstexprlà thời điểm mà các giá trị khởi tạo của chúng được biết đến (được đánh giá). Mặc dù các giá trị của constcác biến có thể được đánh giá ở cả thời gian biên dịch và thời gian chạy, constexprluôn được đánh giá ở thời gian biên dịch. Ví dụ:

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.

Lợi thế chính để biết giá trị được biết vào thời gian biên dịch hay thời gian chạy là thực tế là các hằng số thời gian biên dịch có thể được sử dụng bất cứ khi nào cần các hằng số thời gian biên dịch. Chẳng hạn, C ++ không cho phép bạn chỉ định mảng C với độ dài thay đổi.

int temp=rand(); // temp is generated by the the random generator at runtime.

int array1[10]; // OK.
int array2[temp]; // ERROR.

Vì vậy, nó có nghĩa là:

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.

Vì vậy, constcác biến có thể định nghĩa cả hai hằng số thời gian biên dịch như thế size1có thể được sử dụng để chỉ định kích thước mảng và hằng số thời gian chạy giống như size2chỉ được biết trong thời gian chạy và không thể được sử dụng để xác định kích thước mảng. Mặt khác, constexprluôn xác định các hằng số thời gian biên dịch có thể chỉ định kích thước mảng.

Cả hai constconstexprcó thể được áp dụng cho các chức năng quá. Một constchức năng phải là một hàm thành viên (phương pháp, điều hành), nơi ứng dụng của constphương tiện từ khóa đó phương pháp này không thể thay đổi các giá trị của thành viên của họ (không tĩnh) lĩnh vực. Ví dụ.

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.
   }
};

A constexprlà một khái niệm khác. Nó đánh dấu một hàm (thành viên hoặc không phải thành viên) là hàm có thể được đánh giá tại thời gian biên dịch nếu các hằng số thời gian biên dịch được truyền làm đối số của chúng . Ví dụ bạn có thể viết cái này.

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.

Nhân tiện, các constexprhàm là các hàm C ++ thông thường có thể được gọi ngay cả khi các đối số không liên tục được truyền. Nhưng trong trường hợp đó, bạn sẽ nhận được các giá trị không phải là 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.

Cũng constexprcó thể được áp dụng cho các hàm thành viên (phương thức), toán tử và thậm chí các hàm tạo. Ví dụ.

class test2
{
    static constexpr int function(int value)
    {
        return(value+1);
    }

    void f()
    {
        int x[function(10)];


    }
};

Một mẫu 'điên' hơn.

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.

Ngoài ra, trong C, constexpr inttồn tại nhưng nó được đánh vầnconst int
tò mò

8

Như @ 0x499602d2 đã chỉ ra, constchỉ đảm bảo rằng giá trị không thể thay đổi sau khi khởi tạo trong khi như constexpr(được giới thiệu trong C ++ 11) đảm bảo biến là hằng số thời gian biên dịch.
Hãy xem xét ví dụ sau (từ 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

A const int varcó thể được đặt tự động thành một giá trị trong thời gian chạy và một khi nó được đặt thành giá trị đó, nó không thể thay đổi được nữa.

Không constexpr int varthể được thiết lập động khi chạy, nhưng thay vào đó, tại thời gian biên dịch. Và một khi nó được đặt thành giá trị đó, nó không thể thay đổi được nữa.

Đây là một ví dụ vững chắc:

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
}

Đoạn mã trên biên dịch tốt và tôi đã nhận xét những cái gây ra lỗi.

Các khái niệm chính ở đây cần lưu ý, là các khái niệm về compile timerun time. Những cải tiến mới đã được đưa vào C ++ nhằm mục đích nhiều nhất có thể ** know **vào những thời điểm biên dịch để cải thiện hiệu năng khi chạy.


3

Tôi không nghĩ bất kỳ câu trả lời nào thực sự làm cho nó rõ ràng chính xác những tác dụng phụ của nó, hoặc thực sự, nó là gì.

constexprconsttại không gian tên / phạm vi tệp giống hệt nhau khi được khởi tạo bằng một chữ hoặc biểu thức; nhưng với một hàm, constcó thể được khởi tạo bởi bất kỳ hàm nào, nhưng được constexprkhởi tạo bởi một non-constexpr (một hàm không được đánh dấu bằng constexpr hoặc một biểu thức không constexpr) sẽ tạo ra lỗi trình biên dịch. Cả hai constexprconstđều là liên kết nội bộ cho các biến (thực ra, chúng không tồn tại để đến giai đoạn liên kết nếu biên dịch -O1 và mạnh hơn và statickhông buộc trình biên dịch phát ra biểu tượng liên kết nội bộ (cục bộ) cho consthoặc constexprkhi -O1 hoặc mạnh hơn, lần duy nhất thực hiện việc này là nếu bạn lấy địa chỉ của biến. constconstexpr sẽ là ký hiệu bên trong trừ khi được biểu thị bằng externnghĩa làextern constexpr/const int i = 3;cần được sử dụng). Trên một hàm, .constexprlàm cho hàm vĩnh viễn không bao giờ đạt đến giai đoạn liên kết (bất kể externhoặc inlinetrong định nghĩa hoặc -O0 hoặc -Oast), trong khi constkhông bao giờ thực hiện staticinlinechỉ có hiệu ứng này trên -O1 trở lên. Khi một biến const/ constexprđược khởi tạo bởi một constexprhàm, tải luôn được tối ưu hóa với bất kỳ cờ tối ưu hóa nào, nhưng nó không bao giờ được tối ưu hóa nếu hàm chỉ statichoặc inlinehoặc nếu biến không phải là const/constexpr

Biên dịch chuẩn (-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;
} 

biên dịch thành

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):
        . 
        . 
        . 

Tuy nhiên

#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;
}

Biên dịch thành

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

Điều này cho thấy rõ rằng constexprviệc khởi tạo const/constexprbiến phạm vi tệp xảy ra tại thời điểm biên dịch và không tạo ra ký hiệu toàn cục, trong khi việc không sử dụng nó gây ra khởi tạo xảy ra trước đómain chạy.

Biên dịch bằng -Ofast

Ngay cả -Ofast cũng không tối ưu hóa tải! https://godbolt.org/z/r-mhif , vì vậy bạn cần constexpr


constexprcác hàm cũng có thể được gọi từ bên trong các constexprhàm khác cho cùng kết quả. constexprtrên một hàm cũng ngăn việc sử dụng bất cứ thứ gì không thể thực hiện được trong thời gian biên dịch trong hàm; ví dụ, một cuộc gọi đến <<nhà điều hành trên std::cout.

constexprtại phạm vi khối hoạt động giống nhau ở chỗ nó tạo ra lỗi nếu được khởi tạo bởi hàm không phải là constexpr; giá trị cũng được thay thế ngay lập tức.

Cuối cùng, mục đích chính của nó giống như hàm nội tuyến của C, nhưng nó chỉ hiệu quả khi hàm được sử dụng để khởi tạo các biến phạm vi tệp (mà các hàm không thể thực hiện trên C, nhưng chúng có thể khởi tạo động của tệp- biến phạm vi), ngoại trừ chức năng không thể xuất ký hiệu toàn cục / cục bộ sang trình liên kết, ngay cả khi sử dụng extern/static, mà bạn có thể với inlinetrên C; Các hàm gán biến phạm vi khối có thể được nội tuyến đơn giản bằng cách sử dụng tối ưu hóa -O1 mà không cần constexprtrên C và C ++.


Điểm tốt đẹp trên linker. Nói chung có thể được coi là an toàn hơn khi sử dụng constexpr vì nó dẫn đến rò rỉ biểu tượng ít hơn?
Neil McGill

1
@NeilMcGill không thực sự vì nội tuyến và tĩnh sẽ khiến trình biên dịch không phát ra biểu tượng cục bộ để nhân nếu biên dịch bằng -O1 hoặc mạnh hơn. Constexpr là cái duy nhất tối ưu hóa tải cho val, nhưng khác với nó là giống hệt với việc đặt tĩnh hoặc nội tuyến trước hàm. Tôi đã quên một cái gì đó khác là tốt. Constexpr là từ khóa duy nhất không phát ra ký hiệu cho hàm trên -O0, tĩnh và nội tuyến
Lewis Kelsey

1

Trước hết, cả hai đều là vòng loại trong c ++. Một biến khai báo const phải được khởi tạo và không thể thay đổi trong tương lai. Do đó, nói chung một biến được khai báo là const sẽ có giá trị ngay cả trước khi biên dịch.

Nhưng, đối với constexpr thì hơi khác một chút.

Đối với constexpr, bạn có thể đưa ra một biểu thức có thể được đánh giá trong quá trình biên dịch chương trình.

Rõ ràng, biến được khai báo là constexper không thể thay đổi trong tương lai giống như const.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.