const vs constexpr trên các biến


303

Có sự khác biệt giữa các định nghĩa sau đây?

const     double PI = 3.141592653589793;
constexpr double PI = 3.141592653589793;

Nếu không, phong cách nào được ưa thích trong C ++ 11?



Cả hai đều là hằng số thời gian biên dịch. Nhưng bạn có thể thực hiện một const_cast đầu tiên và viết cho nó. Nhưng nó sẽ được tối ưu hóa bởi bất kỳ trình biên dịch nào vì điều này không ảnh hưởng đến "đọc" khi chúng xảy ra vào thời gian biên dịch.
Bonita Montero

Câu trả lời:


347

Tôi tin rằng có một sự khác biệt. Hãy đổi tên chúng để chúng ta có thể nói về chúng dễ dàng hơn:

const     double PI1 = 3.141592653589793;
constexpr double PI2 = 3.141592653589793;

Cả hai PI1PI2là hằng số, có nghĩa là bạn không thể sửa đổi chúng. Tuy nhiên chỉ PI2 là một hằng số thời gian biên dịch. Nó sẽ được khởi tạo tại thời gian biên dịch. PI1có thể được khởi tạo tại thời gian biên dịch hoặc thời gian chạy. Hơn nữa, chỉ PI2 có thể được sử dụng trong ngữ cảnh yêu cầu hằng số thời gian biên dịch. Ví dụ:

constexpr double PI3 = PI1;  // error

nhưng:

constexpr double PI3 = PI2;  // ok

và:

static_assert(PI1 == 3.141592653589793, "");  // error

nhưng:

static_assert(PI2 == 3.141592653589793, "");  // ok

Bạn nên sử dụng loại nào? Sử dụng bất cứ điều gì đáp ứng nhu cầu của bạn. Bạn có muốn đảm bảo rằng bạn có hằng số thời gian biên dịch có thể được sử dụng trong các ngữ cảnh yêu cầu hằng số thời gian biên dịch không? Bạn có muốn khởi tạo nó với một tính toán được thực hiện trong thời gian chạy không? Vân vân.


60
Bạn có chắc không? Bởi vì const int N = 10; char a[N];các công trình và giới hạn mảng phải là hằng số thời gian biên dịch.
fredoverflow

10
Tôi chắc chắn theo như những ví dụ tôi đã viết (đã kiểm tra từng cái trước khi đăng). Tuy nhiên, trình biên dịch của tôi không cho phép tôi chuyển đổi PI1thành hằng số tích phân thời gian biên dịch để sử dụng trong một mảng, nhưng không sử dụng như một tham số mẫu tích phân không loại. Vì vậy, khả năng chuyển đổi thời gian biên dịch PI1thành một loại tích phân có vẻ hơi khó hiểu đối với tôi.
Howard Hinnant

34
@FredOverflow: Các chỉ mục mảng không const đã "hoạt động" được khoảng một thập kỷ (ví dụ như có phần mở rộng g ++ cho điều đó), nhưng điều đó không có nghĩa là C ++ hợp pháp (mặc dù một số tiêu chuẩn C hoặc C ++ gần đây đã biến nó thành hợp pháp , tôi quên cái nào). Đối với sự khác biệt về hằng số compXLime, tham số mẫu và sử dụng làm trình enumkhởi tạo là hai khác biệt đáng chú ý duy nhất giữa constconstexpr(và không hoạt động cho doubledù sao).
Damon

17
Đoạn 4 của 5.19 Biểu thức không đổi [expr.const] cũng là một ghi chú (không quy tắc) nêu rõ rằng việc triển khai được phép thực hiện số học dấu phẩy động khác nhau (ví dụ về độ chính xác) trong thời gian biên dịch so với thời gian chạy. Vì vậy 1 / PI11 / PI2có thể mang lại kết quả khác nhau. Tôi không nghĩ rằng kỹ thuật này khá quan trọng như lời khuyên trong câu trả lời này.
Luc Danton

4
Nhưng nó constexpr double PI3 = PI1;hoạt động chính xác cho tôi. (MSVS2013 CTP). Tôi đang làm gì sai?
NuPagadi

77

Không có sự khác biệt ở đây, nhưng nó quan trọng khi bạn có một loại có hàm tạo.

struct S {
    constexpr S(int);
};

const S s0(0);
constexpr S s1(1);

s0là một hằng số, nhưng nó không hứa sẽ được khởi tạo tại thời gian biên dịch. s1được đánh dấu constexpr, vì vậy nó là một hằng số và, bởi vì hàm tạo của nó Scũng được đánh dấu constexpr, nó sẽ được khởi tạo tại thời gian biên dịch.

Chủ yếu là vấn đề này khi khởi tạo trong thời gian chạy sẽ tốn thời gian và bạn muốn đẩy công việc đó lên trình biên dịch, nơi nó cũng tốn thời gian, nhưng không làm chậm thời gian thực hiện của chương trình được biên dịch


3
Tôi đồng ý: kết luận mà tôi đưa ra là constexprsẽ dẫn đến chẩn đoán nếu việc tính toán thời gian biên dịch của đối tượng là không thể. Điều ít rõ ràng hơn là liệu một hàm mong đợi một tham số không đổi có thể được thực thi tại thời gian biên dịch hay không nếu tham số được khai báo là constvà không phải là constexpr: tức là, sẽ constexpr int foo(S)được thực thi tại thời gian biên dịch nếu tôi gọi foo(s0)?
Matthieu M.

4
@MatthieuM: Tôi nghi ngờ liệu foo(s0)sẽ được thực thi tại thời gian biên dịch, nhưng bạn không bao giờ biết: trình biên dịch được phép thực hiện tối ưu hóa như vậy. Chắc chắn, không gcc 4.7.2 cũng không kêu vang 3.2 cho phép tôi để biên dịchconstexpr a = foo(s0);
rici

50

constexpr chỉ ra một giá trị không đổi và được biết đến trong quá trình biên dịch.
const chỉ ra một giá trị không đổi; nó không bắt buộc phải biết trong quá trình biên dịch.

int sz;
constexpr auto arraySize1 = sz;    // error! sz's value unknown at compilation
std::array<int, sz> data1;         // error! same problem

constexpr auto arraySize2 = 10;    // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr

Lưu ý rằng const không cung cấp bảo đảm giống như constexpr, vì các đối tượng const không cần phải được khởi tạo với các giá trị được biết trong quá trình biên dịch.

int sz;
const auto arraySize = sz;       // fine, arraySize is const copy of sz
std::array<int, arraySize> data; // error! arraySize's value unknown at compilation

Tất cả các đối tượng constexpr là const, nhưng không phải tất cả các đối tượng const là constexpr.

Nếu bạn muốn trình biên dịch đảm bảo rằng một biến có một giá trị có thể được sử dụng trong các ngữ cảnh yêu cầu các hằng số thời gian biên dịch, công cụ để tiếp cận là constexpr chứ không phải const.


2
Tôi thích lời giải thích của bạn rất nhiều..có thể bạn vui lòng bình luận thêm về trường hợp chúng ta có thể cần sử dụng hằng số thời gian biên dịch trong các tình huống thực tế.
Mayukh Sarkar

1
@MayukhSarkar Đơn giản chỉ cần Google C ++ tại sao constexpr , ví dụ stackoverflow.com/questions/4748083/ chủ
underscore_d

18

Một constexpr liên tục mang tính biểu tượng phải nhận được một giá trị được biết đến tại thời gian biên dịch. Ví dụ:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    constexpr int c2 = n+7;   // Error: we don’t know the value of c2
    // ...
}

Để xử lý trường hợp giá trị của một “biến” được khởi tạo với một giá trị không được biết đến tại thời gian biên dịch nhưng không bao giờ thay đổi sau khi khởi tạo, C ++ cung cấp một hình thức thứ hai của hằng số (một const ). Ví dụ:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    const int c2 = n+7; // OK, but don’t try to change the value of c2
    // ...
    c2 = 7; // error: c2 is a const
}

Các biến const const như vậy rất phổ biến vì hai lý do:

  1. C ++ 98 không có constexpr, vì vậy mọi người đã sử dụng const .
  2. Mục danh sách Biến biến không phải là biểu thức không đổi (giá trị của chúng không được biết tại thời điểm biên dịch) nhưng không thay đổi giá trị sau khi khởi tạo tự chúng rất hữu ích.

Tham khảo: "Lập trình: Nguyên tắc và thực hành sử dụng C ++" của Stroustrup


25
Có lẽ bạn nên đề cập rằng văn bản trong câu trả lời của bạn được lấy nguyên văn từ "Lập trình: Nguyên tắc và Thực hành sử dụng C ++" của Stroustrup
Aky
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.