Tôi nên sử dụng macro ở đâu và tôi thích constexpr ở đâu? Về cơ bản chúng không giống nhau sao?
#define MAX_HEIGHT 720
vs
constexpr unsigned int max_height = 720;
Tôi nên sử dụng macro ở đâu và tôi thích constexpr ở đâu? Về cơ bản chúng không giống nhau sao?
#define MAX_HEIGHT 720
vs
constexpr unsigned int max_height = 720;
Câu trả lời:
Về cơ bản chúng không giống nhau sao?
Không. Hoàn toàn không. Thậm chí không gần.
Ngoài thực tế macro của bạn là một int
và của bạn constexpr unsigned
là một unsigned
, có những khác biệt quan trọng và macro chỉ có một lợi thế.
Macro được xác định bởi bộ tiền xử lý và được thay thế đơn giản vào mã mỗi khi nó xảy ra. Bộ tiền xử lý ngu ngốc và không hiểu cú pháp hoặc ngữ nghĩa C ++. Macro bỏ qua các phạm vi như không gian tên, lớp hoặc khối chức năng, vì vậy bạn không thể sử dụng tên cho bất kỳ thứ gì khác trong tệp nguồn. Điều đó không đúng đối với một hằng được xác định là một biến C ++ thích hợp:
#define MAX_HEIGHT 720
constexpr int max_height = 720;
class Window {
// ...
int max_height;
};
Sẽ tốt khi có một biến thành viên được gọi max_height
bởi vì nó là một thành viên của lớp và do đó có một phạm vi khác và khác biệt với một trong phạm vi không gian tên. Nếu bạn đã cố gắng sử dụng lại tên MAX_HEIGHT
cho thành viên thì bộ xử lý tiền xử lý sẽ thay đổi nó thành vô nghĩa này sẽ không biên dịch:
class Window {
// ...
int 720;
};
Đây là lý do tại sao bạn phải đặt macro UGLY_SHOUTY_NAMES
để đảm bảo chúng nổi bật và bạn có thể cẩn thận trong việc đặt tên chúng để tránh đụng độ. Nếu bạn không sử dụng macro một cách không cần thiết, bạn không phải lo lắng về điều đó (và không cần phải đọc SHOUTY_NAMES
).
Nếu bạn chỉ muốn một hằng số bên trong một hàm, bạn không thể làm điều đó với macro, bởi vì bộ tiền xử lý không biết hàm là gì hoặc ý nghĩa của nó ở bên trong nó. Để giới hạn macro chỉ ở một phần nhất định của tệp, bạn cần #undef
lại macro :
int limit(int height) {
#define MAX_HEIGHT 720
return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}
So sánh với điều hợp lý hơn nhiều:
int limit(int height) {
constexpr int max_height = 720;
return std::max(height, max_height);
}
Tại sao bạn thích macro hơn?
Biến constexpr là một biến để nó thực sự tồn tại trong chương trình và bạn có thể làm những việc C ++ bình thường như lấy địa chỉ của nó và liên kết một tham chiếu đến nó.
Mã này có hành vi không xác định:
#define MAX_HEIGHT 720
int limit(int height) {
const int& h = std::max(height, MAX_HEIGHT);
// ...
return h;
}
Vấn đề là đó MAX_HEIGHT
không phải là một biến, vì vậy để gọi std::max
một tạm thời int
phải được tạo bởi trình biên dịch. Tham chiếu được trả về std::max
sau đó có thể tham chiếu đến tạm thời đó, không tồn tại sau khi kết thúc câu lệnh đó, do đó return h
truy cập bộ nhớ không hợp lệ.
Vấn đề đó chỉ đơn giản là không tồn tại với một biến thích hợp, vì nó có một vị trí cố định trong bộ nhớ mà không biến mất:
int limit(int height) {
constexpr int max_height = 720;
const int& h = std::max(height, max_height);
// ...
return h;
}
(Trong thực tế, bạn có thể tuyên bố là int h
không const int& h
nhưng vấn đề có thể phát sinh trong những bối cảnh phức tạp hơn.)
Thời điểm duy nhất để thích macro là khi bạn cần bộ tiền xử lý hiểu giá trị của nó, để sử dụng trong các #if
điều kiện, ví dụ:
#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif
Bạn không thể sử dụng một biến ở đây, vì bộ tiền xử lý không hiểu cách tham chiếu đến các biến theo tên. Nó chỉ hiểu những thứ cơ bản rất cơ bản như mở rộng macro và các chỉ thị bắt đầu bằng #
(như #include
và #define
và #if
).
Nếu bạn muốn một hằng số có thể được hiểu bởi bộ tiền xử lý thì bạn nên sử dụng bộ tiền xử lý để định nghĩa nó. Nếu bạn muốn một hằng số cho mã C ++ bình thường, hãy sử dụng mã C ++ bình thường.
Ví dụ trên chỉ để chứng minh điều kiện của bộ xử lý trước, nhưng ngay cả đoạn mã đó cũng có thể tránh sử dụng bộ tiền xử lý:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
constexpr
biến không cần chiếm bộ nhớ cho đến khi địa chỉ của nó (một con trỏ / tham chiếu) được lấy; nếu không, nó có thể được tối ưu hóa hoàn toàn (và tôi nghĩ có thể có Standardese đảm bảo điều đó). Tôi muốn nhấn mạnh điều này để mọi người không tiếp tục sử dụng 'bản enum
hack' cũ kỹ, kém chất lượng vì một ý tưởng sai lầm rằng một thứ tầm thường constexpr
không yêu cầu lưu trữ vẫn sẽ chiếm một số.
int height
vấn đề của bạn sẽ giống như macro, vì phạm vi của nó được gắn với hàm, về cơ bản cũng tạm thời. 3. Nhận xét ở trên, "const int & h sẽ kéo dài thời gian tồn tại của tạm thời" là đúng.
limit
, vấn đề là giá trị trả về của std::max
. 2. vâng, đó là lý do tại sao nó không trả về một tham chiếu. 3. sai, xem liên kết coliru ở trên.
const int& h = max(x, y);
và max
trả về bằng giá trị thì thời gian tồn tại của giá trị trả về sẽ được kéo dài. Không phải bởi kiểu trả về, mà bởi kiểu const int&
nó bị ràng buộc. Những gì tôi đã viết là chính xác.
Nói chung, bạn nên sử dụng constexpr
bất cứ khi nào bạn có thể và chỉ macro nếu không có giải pháp nào khác.
Macro là một sự thay thế đơn giản trong mã và vì lý do này, chúng thường tạo ra xung đột (ví dụ: max
macro windows.h vs std::max
). Ngoài ra, một macro hoạt động có thể dễ dàng được sử dụng theo cách khác, sau đó có thể gây ra các lỗi biên dịch lạ. (ví dụ: Q_PROPERTY
được sử dụng trên các thành viên cấu trúc)
Do tất cả những điều không chắc chắn đó, cách tốt nhất là tránh macro, giống như cách bạn thường tránh gotos.
constexpr
được định nghĩa về mặt ngữ nghĩa, và do đó thường tạo ra ít vấn đề hơn.
#if
tức là những thứ mà bộ tiền xử lý thực sự hữu ích. Định nghĩa một hằng số không phải là một trong những điều mà bộ tiền xử lý hữu ích, trừ khi hằng số đó phải là một macro vì nó được sử dụng trong các điều kiện sử dụng bộ tiền xử lý #if
. Nếu hằng được sử dụng trong mã C ++ bình thường (không phải chỉ thị tiền xử lý) thì hãy sử dụng biến C ++ bình thường, không phải macro tiền xử lý.
Câu trả lời tuyệt vời của Jonathon Wakely . Tôi cũng khuyên bạn nên xem câu trả lời của jogojapan về sự khác biệt giữa const
và constexpr
trước khi bạn đi xem xét việc sử dụng macro.
Macro là ngu ngốc, nhưng theo một cách tốt . Rõ ràng ngày nay chúng là một công cụ hỗ trợ khi bạn muốn các phần rất cụ thể trong mã của mình chỉ được biên dịch khi có một số tham số xây dựng nhất định được "xác định". Thông thường, tất cả những gì có nghĩa là đang diễn tên macro của bạn, hoặc tốt hơn nữa, chúng ta hãy gọi nó là một Trigger
, và những thứ thêm thích, /D:Trigger
, -DTrigger
, vv để các công cụ xây dựng được sử dụng.
Mặc dù có nhiều cách sử dụng khác nhau cho macro, nhưng đây là hai cách tôi thấy thường xuyên nhất mà không phải là cách làm xấu / lỗi thời:
Vì vậy, mặc dù trong trường hợp của OP, bạn có thể thực hiện cùng một mục tiêu là xác định số nguyên có constexpr
hoặc a MACRO
, nhưng không chắc cả hai sẽ có sự chồng chéo khi sử dụng các quy ước hiện đại. Dưới đây là một số cách sử dụng macro phổ biến vẫn chưa bị loại bỏ.
#if defined VERBOSE || defined DEBUG || defined MSG_ALL
// Verbose message-handling code here
#endif
Một ví dụ khác cho việc sử dụng macro, giả sử bạn có một số phần cứng sắp phát hành hoặc có thể là một thế hệ cụ thể của nó có một số cách giải quyết phức tạp mà những phần cứng khác không yêu cầu. Chúng tôi sẽ xác định macro này là GEN_3_HW
.
#if defined GEN_3_HW && defined _WIN64
// Windows-only special handling for 64-bit upcoming hardware
#elif defined GEN_3_HW && defined __APPLE__
// Special handling for macs on the new hardware
#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__
// Greetings, Outlander! ;)
#else
// Generic handling
#endif