Khi nào bạn nên sử dụng khả năng constexpr trong C ++ 11?


337

Dường như với tôi rằng việc có một "hàm luôn trả về 5" sẽ phá vỡ hoặc làm loãng ý nghĩa của việc "gọi một hàm". Phải có một lý do, hoặc cần một khả năng này hoặc nó sẽ không có trong C ++ 11. Tại sao nó ở đó?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

Dường như với tôi rằng nếu tôi đã viết một hàm trả về giá trị theo nghĩa đen và tôi đã đưa ra một đánh giá mã, ai đó sẽ nói với tôi, sau đó, tôi nên khai báo một giá trị không đổi thay vì viết return 5.


28
Bạn có thể định nghĩa một hàm đệ quy trả về a constexprkhông? Nếu vậy, tôi có thể thấy một cách sử dụng.
vào

20
Tôi tin rằng câu hỏi nên nêu "tại sao lại giới thiệu một từ khóa mới (!) Nếu trình biên dịch có thể tự suy luận xem một hàm có thể được đánh giá trong thời gian biên dịch hay không". Có nó "được đảm bảo bởi một từ khóa" nghe có vẻ tốt, nhưng tôi nghĩ rằng tôi muốn nó được đảm bảo bất cứ khi nào có thể, mà không cần từ khóa.
Kos

6
@Kos: Ai đó là người trò chuyện THÊM với nội bộ C ++ có lẽ sẽ thích câu hỏi của bạn hơn, nhưng câu hỏi của tôi xuất phát từ quan điểm của một người đã viết mã C trước đây, nhưng không quen thuộc với các từ khóa C ++ 2011, cũng không phải chi tiết triển khai trình biên dịch C ++ . Có thể lý giải về tối ưu hóa trình biên dịch và khấu trừ biểu thức liên tục là một chủ đề cho câu hỏi người dùng nâng cao hơn câu hỏi này.
Warren P

8
@Kos Tôi đã suy nghĩ theo cùng một dòng với bạn, và câu trả lời tôi đưa ra là, không có constexpr, làm thế nào bạn (dễ dàng) biết rằng trình biên dịch thực sự biên dịch hàm thời gian cho bạn? Tôi cho rằng bạn có thể kiểm tra đầu ra lắp ráp để xem nó đã làm gì, nhưng sẽ dễ dàng hơn khi nói với trình biên dịch rằng bạn yêu cầu tối ưu hóa đó và nếu vì lý do nào đó, nó không thể làm điều đó cho bạn, nó sẽ cung cấp cho bạn một trình biên dịch tốt lỗi thay vì âm thầm không tối ưu hóa nơi bạn mong đợi để tối ưu hóa.
Jeremy Friesner

3
@Kos: Bạn có thể nói điều tương tự const. Trong thực tế, ý định bắt buộchữu ích ! Kích thước mảng là ví dụ điển hình.
Các cuộc đua nhẹ nhàng trong quỹ đạo

Câu trả lời:


302

Giả sử nó làm một cái gì đó phức tạp hơn một chút.

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

Bây giờ bạn có một cái gì đó có thể được đánh giá xuống một hằng số trong khi duy trì khả năng đọc tốt và cho phép xử lý phức tạp hơn một chút so với chỉ đặt một hằng số cho một số.

Về cơ bản, nó cung cấp một sự trợ giúp tốt cho khả năng bảo trì vì nó trở nên rõ ràng hơn những gì bạn đang làm. Lấy max( a, b )ví dụ:

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

Đây là một lựa chọn khá đơn giản nhưng điều đó có nghĩa là nếu bạn gọi maxvới các giá trị không đổi thì nó được tính toán rõ ràng vào thời gian biên dịch chứ không phải trong thời gian chạy.

Một ví dụ tốt khác sẽ là một DegreesToRadianschức năng. Mọi người đều tìm thấy độ dễ đọc hơn radian. Mặc dù bạn có thể biết rằng 180 độ là radian nhưng nó được viết rõ ràng hơn như sau:

const float oneeighty = DegreesToRadians( 180.0f );

Nhiều thông tin hay ở đây:

http://en.cppreference.com/w/cpp/lingu/constexpr


18
Điểm tuyệt vời với nó báo cho trình biên dịch thử và tính giá trị tại thời gian biên dịch. Tôi tò mò tại sao const không cung cấp chức năng này khi tối ưu hóa cụ thể được chỉ định? Hay không?
TamusJRoyce

11
@Tamus: Thường thì nó sẽ nhưng không bắt buộc. constexpr bắt buộc trình biên dịch và sẽ phát sinh lỗi nếu không thể.
Goz

20
Tôi thấy nó bây giờ. Tội lỗi (0,5) là khác. Điều này thay thế các macro C gọn gàng.
Warren P

10
Tôi có thể xem đây là một câu hỏi phỏng vấn mới: Giải thích sự khác biệt giữa từ khóa const và constexpr.
Warren P

2
Như một cách để ghi lại điểm này cho bản thân tôi, tôi đã viết mã tương tự như trên và một lần nữa với chức năng là "const" chứ không phải là "constexpr". Khi tôi đang sử dụng Clang3.3, lỗi -pedantic-lỗi và -std = c ++ 11, tôi dự đoán cái sau sẽ không biên dịch. Nó được biên dịch và chạy như trong trường hợp "constexpr". Bạn có cho rằng đây là một phần mở rộng clang hoặc đã có một điều chỉnh cho thông số C ++ 11 kể từ khi bài đăng này được trả lời?
Arbalest

144

Giới thiệu

constexpr đã không được giới thiệu như một cách để nói với việc thực hiện rằng một cái gì đó có thể được đánh giá trong bối cảnh đòi hỏi một biểu thức không đổi ; việc triển khai tuân thủ đã có thể chứng minh điều này trước C ++ 11.

Một cái gì đó mà việc thực hiện không thể chứng minh là ý định của một đoạn mã nhất định:

  • Nhà phát triển muốn thể hiện điều gì với thực thể này?
  • Chúng ta có nên mù quáng cho phép mã được sử dụng trong một biểu thức không đổi , chỉ vì nó xảy ra để làm việc?

Thế giới sẽ ra sao nếu không có constexpr ?

Giả sử bạn đang phát triển một thư viện và nhận ra rằng bạn muốn có thể tính tổng của mọi số nguyên trong khoảng (0,N].

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

Sự thiếu ý định

Một trình biên dịch có thể dễ dàng chứng minh rằng hàm trên có thể gọi được trong một biểu thức không đổi nếu biết được đối số được truyền trong khi dịch; nhưng bạn đã không tuyên bố đây là một ý định - nó chỉ là trường hợp.

Bây giờ có người khác đến, đọc chức năng của bạn, thực hiện phân tích tương tự như trình biên dịch; " Ồ, chức năng này có thể sử dụng được trong một biểu thức không đổi!" và viết đoạn mã sau.

T arr[f(10)]; // freakin' magic

Tối ưu hóa

Bạn, với tư cách là một nhà phát triển thư viện "tuyệt vời" , quyết định rằng fnên lưu trữ kết quả khi được gọi; Ai muốn tính toán cùng một tập hợp các giá trị?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

Kết quả

Bằng cách giới thiệu tối ưu hóa ngớ ngẩn của bạn, bạn vừa phá vỡ mọi cách sử dụng chức năng của mình trong một bối cảnh có biểu thức không đổi đòi hỏi phải có .

Bạn không bao giờ hứa rằng hàm này có thể sử dụng được trong một biểu thức không đổi , và nếu không constexprcó cách nào sẽ cung cấp lời hứa như vậy.


Vậy, tại sao chúng ta cần constexpr?

Việc sử dụng chính của constexpr là khai báo ý định .

Nếu một thực thể không được đánh dấu là constexpr- nó không bao giờ được sử dụng trong một biểu thức không đổi ; và thậm chí nếu có, chúng tôi dựa vào trình biên dịch để chẩn đoán bối cảnh đó (vì nó coi thường ý định của chúng tôi).


25
Đây có lẽ là câu trả lời chính xác, vì những thay đổi gần đây trong C ++ 14 và C ++ 17 cho phép phạm vi ngôn ngữ rộng hơn nhiều được sử dụng trong các constexprbiểu thức. Nói cách khác, hầu hết mọi thứ đều có thể được chú thích constexpr(có thể một ngày nào đó nó sẽ biến mất vì điều này?), Và trừ khi người ta có tiêu chí khi nào nên sử dụng constexprhay không, hầu hết tất cả các mã sẽ được viết như vậy .
alecov

4
@alecov Chắc chắn không phải tất cả mọi thứ ... I/O, syscalldynamic memory allocationchắc chắn không thể được đánh dấu là constexprBên cạnh đó, không phải mọi thứ nên được constexpr.
JiaHao Xu

1
@alecov Một số hàm có nghĩa là được thực thi trong thời gian chạy và vô nghĩa để làm điều này vào thời gian biên dịch.
JiaHao Xu

1
Tôi cũng thích câu trả lời này tốt nhất. Biên dịch đánh giá thời gian là một tối ưu hóa gọn gàng, nhưng những gì bạn thực sự nhận được constexprlà sự đảm bảo của một số loại hành vi. Cũng giống như constvậy.
Tomáš Zato - Phục hồi Monica

Trình biên dịch nào cho phép phiên bản int f (int n) { return n > 0 ? n + f (n-1) : n;} T arr[f(10)]; không có constexpr này mà tôi không thể biên dịch ở bất cứ đâu?
Jer

91

Hãy std::numeric_limits<T>::max(): vì lý do gì, đây là một phương pháp. constexprsẽ có lợi ở đây

Một ví dụ khác: bạn muốn khai báo một mảng C (hoặc a std::array) lớn như một mảng khác. Cách để làm điều này vào lúc này là như vậy:

int x[10];
int y[sizeof x / sizeof x[0]];

Nhưng nó sẽ không tốt hơn để có thể viết:

int y[size_of(x)];

Nhờ constexpr, bạn có thể:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}

1
+1 để sử dụng mẫu đẹp, nhưng nó hoạt động chính xác như nhau mà không cần constexpr, phải không?
Kos

21
@Kos: Không. Nó sẽ trả về giá trị thời gian chạy. constexprbuộc trình biên dịch làm cho hàm trả về giá trị thời gian biên dịch (nếu có thể).
deft_code

14
@Kos: không có constexprnó không thể được sử dụng trong khai báo kích thước mảng, cũng như đối số khuôn mẫu, bất kể kết quả của lệnh gọi hàm có phải là hằng số thời gian biên dịch hay không. Hai cái này về cơ bản là các trường hợp sử dụng duy nhất cho constexprnhưng ít nhất trường hợp sử dụng đối số khuôn mẫu là loại quan trọng.
Konrad Rudolph

2
"Vì bất kỳ lý do gì, đây là một phương thức": Lý do là chỉ có các số nguyên thời gian biên dịch trong C ++ 03, nhưng không có loại thời gian biên dịch nào khác, vì vậy chỉ có một phương thức có thể hoạt động cho các loại tùy ý trước C ++ 11.
Sebastian Mach

5
@LwCui Không, đó không phải là ok ok. GCC mặc định chỉ lỏng lẻo về một số thứ nhất định. Sử dụng -pedantictùy chọn và nó sẽ được gắn cờ là một lỗi.
Konrad Rudolph

19

constexprcác chức năng thực sự tốt đẹp và là một bổ sung tuyệt vời cho c ++. Tuy nhiên, bạn đã đúng khi hầu hết các vấn đề mà nó giải quyết có thể được xử lý liên tục với các macro.

Tuy nhiên, một trong những cách sử dụng constexprkhông có hằng số gõ C ++ 03 tương đương.

// This is bad for obvious reasons.
#define ONE 1;

// This works most of the time but isn't fully typed.
enum { TWO = 2 };

// This doesn't compile
enum { pi = 3.1415f };

// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;

// This is a true constant rvalue
constexpr float pi = 3.1415f;

// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor

struct A
{
   static const int four = 4;
   static const int five = 5;
   constexpr int six = 6;
};

int main()
{
   &A::four; // linker error
   &A::six; // compiler error

   // EXTREMELY subtle linker error
   int i = rand()? A::four: A::five;
   // It not safe use static const class variables with the ternary operator!
}

//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;

12
Bạn có thể vui lòng làm rõ rằng "lỗi liên kết tinh tế TUYỆT VỜI" không? Hoặc ít nhất cung cấp một con trỏ để làm rõ?
enobayram

4
@enobayram, Toán tử ternary lấy địa chỉ của toán hạng. Điều đó không rõ ràng từ mã. Mọi thứ biên dịch tốt, nhưng liên kết không thành công vì địa chỉ fourkhông giải quyết được. Tôi đã phải thực sự đào để tìm ra ai đã lấy địa chỉ của static constbiến của tôi .
deft_code

23
"Điều này là xấu vì lý do rõ ràng": lý do rõ ràng nhất là dấu chấm phẩy, phải không?
TonyK

4
"Lỗi liên kết tinh tế TUYỆT VỜI" khiến tôi hoàn toàn hoang mang. Không fourphải fivetrong phạm vi.
Steven Lu

3
xem thêm enum classloại mới , nó sửa một số vấn đề enum.
ninMonkey

14

Từ những gì tôi đã đọc, nhu cầu về constexpr xuất phát từ một vấn đề trong siêu lập trình. Các lớp đặc điểm có thể có các hằng được biểu diễn dưới dạng hàm, hãy nghĩ: num_limits :: max (). Với constexpr, các loại hàm này có thể được sử dụng trong siêu lập trình, hoặc như giới hạn mảng, v.v.

Một ví dụ khác ngoài đỉnh đầu của tôi sẽ là đối với các giao diện lớp, bạn có thể muốn các kiểu dẫn xuất xác định các hằng số riêng của chúng cho một số thao tác.

Biên tập:

Sau khi tìm hiểu về SO, có vẻ như những người khác đã đưa ra một số ví dụ về những gì có thể xảy ra với constexprs.


"Để trở thành một phần của giao diện bạn phải có chức năng"?
Daniel Earwicker

Bây giờ tôi có thể thấy sự hữu ích của việc này, tôi cảm thấy phấn khích hơn một chút về C ++ 0x. Có vẻ như một điều cũng nghĩ ra. Tôi biết họ phải như vậy. Những ngôn ngữ tiêu chuẩn uber-geek hiếm khi làm những điều ngẫu nhiên.
Warren P

Tôi cảm thấy phấn khích hơn về lambdas, mô hình luồng, initizer_list, tham chiếu giá trị, các mẫu biến đổi, quá tải liên kết mới ... có khá nhiều điều để mong đợi.
luke

1
Ồ vâng, nhưng tôi đã hiểu lambdas / đóng cửa trong một số languges khác. constexprđặc biệt hữu ích hơn trong trình biên dịch với hệ thống đánh giá biểu thức thời gian biên dịch mạnh mẽ. C ++ thực sự không có đồng nghiệp trong miền đó. (đó là một lời khen ngợi mạnh mẽ cho C ++ 11, IMHO)
Warren P

11

Từ bài phát biểu của Stroustrup tại "Đi bản địa 2012":

template<int M, int K, int S> struct Unit { // a unit in the MKS system
       enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit 
struct Value {
       double val;   // the magnitude 
       explicit Value(double d) : val(d) {} // construct a Value from a double 
};

using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second 

constexpr Value<Second> operator"" s(long double d)
   // a f-p literal suffixed by ‘s’
{
  return Value<Second> (d);  
}   

constexpr Value<Second2> operator"" s2(long double d)
  // a f-p literal  suffixed by ‘s2’ 
{
  return Value<Second2> (d); 
}

Speed sp1 = 100m/9.8s; // very fast for a human 
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
Acceleration acc = sp1/0.5s; // too fast for a human

2
Ví dụ này cũng có thể được tìm thấy trong Phát triển phần mềm giấy cho cơ sở hạ tầng của Stroustrup .
Matthieu Poullet

clang-3.3: lỗi: kiểu trả về của hàm constexpr 'Giá trị <Thứ hai>' không phải là kiểu chữ
Mitja

Điều này là tốt nhưng ai đặt chữ trong mã như thế này. Để trình biên dịch của bạn "kiểm tra các đơn vị của bạn" cho bạn sẽ có ý nghĩa nếu bạn đang viết một máy tính tương tác.
bobobobo

5
@bobobobo hoặc nếu bạn đang viết phần mềm điều hướng cho Mars Climate Orbiter, có thể :)
Jeremy Friesner

1
Để làm cho nó biên dịch - 1. Sử dụng dấu gạch dưới trong các hậu tố theo nghĩa đen. 2. thêm toán tử "" _m cho 100_m. 3. sử dụng 100.0_m hoặc thêm quá tải chấp nhận dài không dấu. 4. Khai báo constexpr constructor Value. 5. Thêm toán tử tương ứng / vào lớp Giá trị như thế này: toán tử tự động constexpr / (const Value <Y> & other) const {return Value <Unit <TheUnit :: m - Value <Y> :: TheUnit :: m, TheUnit :: kg - Giá trị <Y> :: TheUnit :: kg, TheUnit :: s - Giá trị <Y> :: TheUnit :: s >> (val / other.val); }. Trong đó TheUnit là typedef cho Đơn vị được thêm vào bên trong lớp Giá trị.
0kcats

8

Một cách sử dụng khác (chưa được đề cập) là các nhà constexprxây dựng. Điều này cho phép tạo các hằng số thời gian biên dịch mà không phải khởi tạo trong thời gian chạy.

const std::complex<double> meaning_of_imagination(0, 42); 

Ghép nối với nghĩa đen do người dùng định nghĩa và bạn có hỗ trợ đầy đủ cho các lớp do người dùng xác định theo nghĩa đen.

3.14D + 42_i;

6

Đã từng có một mẫu với siêu lập trình:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

Tôi tin rằng constexprđã được giới thiệu để cho phép bạn viết các cấu trúc như vậy mà không cần các mẫu và các cấu trúc lạ với chuyên môn hóa, SFINAE và các thứ - nhưng chính xác như bạn sẽ viết một hàm thời gian chạy, nhưng với sự đảm bảo rằng kết quả sẽ được xác định trong quá trình biên dịch -thời gian.

Tuy nhiên, lưu ý rằng:

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

Biên dịch cái này với g++ -O3và bạn sẽ thấy rằngfact(10) thực sự được phát hiện vào thời gian biên dịch!

Trình biên dịch nhận biết VLA (vì vậy trình biên dịch C ở chế độ C99 hoặc trình biên dịch C ++ có phần mở rộng C99) thậm chí có thể cho phép bạn thực hiện:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

Nhưng đó là C ++ không chuẩn vào lúc này - constexprtrông giống như một cách để chống lại điều này (ngay cả khi không có VLA, trong trường hợp trên). Và vẫn còn vấn đề về sự cần thiết phải có các biểu thức hằng "chính thức" làm đối số khuôn mẫu.


Hàm thực tế không được đánh giá tại thời gian biên dịch. Nó cần phải là constexpr và chỉ có một tuyên bố trả về.
Sumant

1
@Sumant: Bạn đúng rằng nó không phải được đánh giá tại thời điểm biên dịch, nhưng đó là! Tôi đã đề cập đến những gì thực sự xảy ra trong trình biên dịch. Biên dịch nó trên GCC gần đây, xem asm kết quả và tự kiểm tra nếu bạn không tin tôi!
Kos

Hãy thử thêm std::array<int, fact(2)>và bạn sẽ thấy thực tế () không được đánh giá tại thời gian biên dịch. Đó chỉ là trình tối ưu hóa GCC đang làm tốt công việc.

1
Đó là những gì tôi nói ... tôi thực sự không rõ ràng? Xem đoạn cuối
Kos

5

Mới bắt đầu chuyển đổi một dự án sang c ++ 11 và đã gặp một tình huống hoàn toàn tốt cho constexpr giúp dọn sạch các phương pháp thay thế để thực hiện cùng một hoạt động. Điểm mấu chốt ở đây là bạn chỉ có thể đặt hàm vào khai báo kích thước mảng khi nó được khai báo constexpr. Có một số tình huống mà tôi có thể thấy điều này rất hữu ích khi tiến lên phía trước với khu vực mã mà tôi tham gia.

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}

4
Điều này có thể được viết bằng nhau: const size_t MaxIPV4StringLạng = sizeof ("255.255.255.255");
Superfly Jon

static inline constexpr const autocó lẽ là tốt hơn
JiaHao Xu

3

Tất cả các câu trả lời khác đều tuyệt vời, tôi chỉ muốn đưa ra một ví dụ tuyệt vời về một điều bạn có thể làm với constexpr thật tuyệt vời. See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) là một trình phân tích cú pháp HTML và công cụ mẫu thời gian biên dịch. Điều này có nghĩa là bạn có thể đặt HTML vào và lấy ra một cây có thể được thao tác. Việc phân tích cú pháp được thực hiện tại thời gian biên dịch có thể cung cấp cho bạn một chút hiệu suất bổ sung.

Từ ví dụ trang github:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}

1

Ví dụ cơ bản của bạn phục vụ anh ta lập luận tương tự như của hằng số. Tại sao sử dụng

static const int x = 5;
int arr[x];

kết thúc

int arr[5];

Bởi vì đó là cách duy trì nhiều hơn. Sử dụng constexpr rất nhiều, nhanh hơn nhiều để viết và đọc so với các kỹ thuật siêu lập trình hiện có.


0

Nó có thể cho phép một số tối ưu hóa mới. consttheo truyền thống là một gợi ý cho hệ thống loại và không thể được sử dụng để tối ưu hóa (ví dụ: constchức năng thành viên có thể const_castvà sửa đổi đối tượng bằng mọi cách, về mặt pháp lý, do đó constkhông thể tin cậy để tối ưu hóa).

constexprcó nghĩa là biểu thức thực sự là hằng số, với điều kiện đầu vào của hàm là const. Xem xét:

class MyInterface {
public:
    int GetNumber() const = 0;
};

Nếu điều này được phơi bày trong một số mô-đun khác, trình biên dịch không thể tin rằng GetNumber()sẽ không trả về các giá trị khác nhau mỗi lần nó được gọi - thậm chí liên tiếp không có các cuộc gọi không liên tục ở giữa - bởi vìconst có thể đã bị loại bỏ trong quá trình thực hiện. (Rõ ràng là bất kỳ lập trình viên nào đã làm điều này nên bị bắn, nhưng ngôn ngữ cho phép nó, do đó trình biên dịch phải tuân thủ các quy tắc.)

Thêm constexpr:

class MyInterface {
public:
    constexpr int GetNumber() const = 0;
};

Bây giờ trình biên dịch có thể áp dụng tối ưu hóa trong đó giá trị trả về GetNumber()được lưu trong bộ nhớ cache và loại bỏ các lệnh gọi bổ sung GetNumber(), bởi vì constexprđảm bảo chắc chắn hơn rằng giá trị trả về sẽ không thay đổi.


Trên thực tế const có thể được sử dụng để tối ưu hóa ... Đó là hành vi không xác định để sửa đổi giá trị được xác định const ngay cả sau const_castIIRC. Tôi hy vọng nó phù hợp với các constchức năng thành viên, nhưng tôi cần kiểm tra điều đó với tiêu chuẩn. Điều này có nghĩa là trình biên dịch có thể thực hiện tối ưu hóa một cách an toàn ở đó.
Kos

1
@Warren: không thành vấn đề nếu việc tối ưu hóa thực sự được thực hiện, nó chỉ được cho phép. @Kos: một sự tinh tế ít được biết đến là nếu đối tượng ban đầu không được khai báo const ( int xvs. const int x), thì có thể sửa đổi nó bằng cách const_castbỏ const trên một con trỏ / tham chiếu đến nó. const_castMặt khác, sẽ luôn gọi hành vi không xác định và vô dụng :) Trong trường hợp này, trình biên dịch không có thông tin về hằng số của đối tượng ban đầu, vì vậy nó không thể biết được.
AshleyBrain

@Kos Tôi không nghĩ const_cast là vấn đề duy nhất ở đây. Phương thức const được phép đọc và thậm chí sửa đổi một biến toàn cục. Ngược lại, ai đó từ luồng không gian cũng có thể sửa đổi đối tượng const giữa các cuộc gọi.
enobayram

1
"= 0" không hợp lệ ở đây và cần được xóa. Tôi sẽ tự làm điều đó, nhưng tôi không chắc điều đó phù hợp với giao thức SO.
KnowIt ALLWannabe

Cả hai ví dụ đều không hợp lệ: cái đầu tiên ( int GetNumber() const = 0;) sẽ khai báo GetNumber()phương thức ảo. Thứ hai ( constexpr int GetNumber() const = 0;) không hợp lệ vì công cụ xác định thuần túy ( = 0) ngụ ý phương thức là ảo, nhưng constexpr không phải là ảo (ref: en.cppreference.com/w/cpp/lingu/constexpr )
stj

-1

Khi nào nên sử dụng constexpr:

  1. Bất cứ khi nào có một hằng số thời gian biên dịch.

Mặc dù tôi đồng ý với bạn, câu trả lời này không giải thích lý do tại sao constexpr nên ưu tiên hơn các macro tiền xử lý hoặc const.
Sneftel

-3

Nó hữu ích cho những thứ như

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

int some_arr[MeaningOfLife()];

Liên kết điều này với một lớp đặc điểm hoặc tương tự và nó trở nên khá hữu ích.


4
Trong ví dụ của bạn, nó cung cấp lợi thế bằng 0 so với hằng số đơn giản, vì vậy nó không thực sự trả lời câu hỏi.
jalf

Đây là một ví dụ giả định, hãy tưởng tượng nếu MeaningOfLife () nhận được giá trị của nó từ một nơi khác, giả sử một hàm khác hoặc #define hoặc chuỗi trị liệu. Bạn có thể không biết những gì nó trả về, nó có thể là mã thư viện. Các ví dụ khác, hãy tưởng tượng một thùng chứa bất biến có phương thức constexpr size (). Bây giờ bạn có thể thực hiện int Array [container.size ()];
Plivesey

2
@plivesey bạn có thể vui lòng chỉnh sửa câu trả lời của bạn với một ví dụ tốt hơn sau đó.
Mukesh
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.