const tĩnh so với #define


212

Có tốt hơn để sử dụng static constvars hơn #definetiền xử lý? Hoặc có thể nó phụ thuộc vào bối cảnh?

Ưu điểm / nhược điểm của từng phương pháp là gì?


14
Scott Meyers bao quát chủ đề này rất độc đáo và kỹ lưỡng. Mục số 2 của anh ấy trong "Hiệu quả phiên bản thứ ba C ++". Hai trường hợp đặc biệt (1) const const được ưu tiên trong phạm vi lớp cho các hằng số cụ thể của lớp; (2) không gian tên hoặc phạm vi ẩn danh const được ưa thích hơn #define.
Eric

2
Tôi thích Enums. Bởi vì nó là lai của cả hai. Không chiếm không gian trừ khi bạn tạo một biến của nó. Nếu bạn chỉ muốn sử dụng như một hằng số, enum là lựa chọn tốt nhất. Nó có loại an toàn trong C / C ++ 11 std và cũng là một hằng số hoàn hảo. #define là loại không an toàn, const chiếm dung lượng nếu trình biên dịch không thể tối ưu hóa nó.
siddhusingh

1
Quyết định của tôi là sử dụng #definehay static const(đối với chuỗi) được điều khiển bởi khía cạnh khởi tạo (nó không được đề cập qua các câu trả lời bên dưới): nếu hằng số chỉ được sử dụng trong đơn vị biên dịch cụ thể, thì tôi sẽ static constsử dụng #define- tránh sử dụng fiasco khởi tạo thứ tự tĩnh isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak

Nếu const, constexprhoặc enumbất kỳ biến thể nào hoạt động trong trường hợp của bạn, thì hãy thích nó hơn#define
Phil1970

@MartinDvorak " tránh fiasco khởi tạo trật tự tĩnh " Làm thế nào đó là một vấn đề cho các hằng số?
tò mò

Câu trả lời:


139

Cá nhân, tôi ghê tởm bộ tiền xử lý, vì vậy tôi luôn đi cùng const.

Ưu điểm chính của a #definelà nó không yêu cầu bộ nhớ để lưu trữ trong chương trình của bạn, vì nó thực sự chỉ là thay thế một số văn bản bằng một giá trị theo nghĩa đen. Nó cũng có lợi thế là nó không có loại, vì vậy nó có thể được sử dụng cho bất kỳ giá trị số nguyên nào mà không tạo ra các cảnh báo.

Ưu điểm của " const" là chúng có thể nằm trong phạm vi và chúng có thể được sử dụng trong các tình huống cần phải chuyển một con trỏ tới một đối tượng.

Tôi không biết chính xác những gì bạn đang nhận được với phần " static" mặc dù. Nếu bạn đang khai báo trên toàn cầu, tôi sẽ đặt nó trong một không gian tên ẩn danh thay vì sử dụng static. Ví dụ

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
Các hằng chuỗi đặc biệt là một trong những hằng số có thể được lợi từ việc #defined, ít nhất là nếu chúng có thể được sử dụng làm "khối xây dựng" cho các hằng chuỗi lớn hơn. Xem trả lời của tôi cho một ví dụ.
AnT

62
Các #definelợi thế của việc không sử dụng bất kỳ bộ nhớ là không chính xác. "60" trong ví dụ phải được lưu trữ ở đâu đó, bất kể đó là static consthay #define. Trong thực tế, tôi đã thấy các trình biên dịch trong đó sử dụng #define gây ra mức tiêu thụ bộ nhớ lớn (chỉ đọc) và const const không sử dụng bộ nhớ không cần thiết.
Gilad Naor

3
Một #define giống như nếu bạn đã nhập nó vào, vì vậy nó chắc chắn không xuất phát từ bộ nhớ.
Reverend

27
@theReverend Các giá trị theo nghĩa đen bằng cách nào đó được miễn tiêu thụ tài nguyên máy? Không, họ chỉ có thể sử dụng chúng theo những cách khác nhau, có thể nó sẽ không xuất hiện trên stack hoặc heap, nhưng đến một lúc nào đó, chương trình được tải vào bộ nhớ cùng với tất cả các giá trị được biên dịch vào nó.
Sqeaky

13
@ gilad-naor, Bạn nói chung nhưng các số nguyên nhỏ như 60 đôi khi thực sự có thể là một loại ngoại lệ một phần. Một số bộ hướng dẫn có khả năng mã hóa số nguyên hoặc một tập hợp con số nguyên trực tiếp trong luồng lệnh. Ví dụ: MIPs thêm ngay lập tức ( cs.umd.edu/ class / s200200 / cmsc311 / Notes / Mips / addi.html ). Trong trường hợp này, một số nguyên #d xác định thực sự có thể được sử dụng không có khoảng trắng vì trong nhị phân được biên dịch, nó chiếm một vài bit dự phòng trong các hướng dẫn phải tồn tại.
ahcox

241

Ưu và nhược điểm giữa #defines, consts và (những gì bạn đã quên) enums, tùy thuộc vào cách sử dụng:

  1. enumS:

    • chỉ có thể cho các giá trị số nguyên
    • các vấn đề xung đột phạm vi / định danh đúng được xử lý độc đáo, đặc biệt là trong các lớp enum C ++ 11 trong đó các bảng liệt kê enum class Xđược phân định theo phạm viX::
    • được gõ mạnh, nhưng với kích thước int được ký hoặc không dấu đủ lớn mà bạn không có quyền kiểm soát trong C ++ 03 (mặc dù bạn có thể chỉ định một trường bit mà chúng nên được đóng gói nếu enum là thành viên của struct / class / union), trong khi C ++ 11 mặc định intnhưng có thể được lập trình viên thiết lập rõ ràng
    • không thể lấy địa chỉ - không có địa chỉ nào vì các giá trị liệt kê được thay thế nội tuyến một cách hiệu quả tại các điểm sử dụng
    • hạn chế sử dụng mạnh hơn (ví dụ: tăng - template <typename T> void f(T t) { cout << ++t; }sẽ không biên dịch, mặc dù bạn có thể bọc một enum vào một lớp với hàm tạo ẩn, toán tử đúc và toán tử do người dùng định nghĩa)
    • mỗi loại hằng được lấy từ enum kèm theo, do đó, template <typename T> void f(T)có được một khởi tạo riêng biệt khi truyền cùng một giá trị số từ các enum khác nhau, tất cả đều khác với bất kỳ f(int)khởi tạo thực tế nào . Mã đối tượng của mỗi hàm có thể giống hệt nhau (bỏ qua phần bù địa chỉ), nhưng tôi không mong đợi trình biên dịch / trình liên kết loại bỏ các bản sao không cần thiết, mặc dù bạn có thể kiểm tra trình biên dịch / trình liên kết của mình nếu bạn quan tâm.
    • ngay cả với typeof / dectype, không thể mong đợi num_limits cung cấp cái nhìn sâu sắc hữu ích về tập hợp các giá trị và kết hợp có ý nghĩa (thực sự, các kết hợp "hợp pháp" thậm chí không được ghi chú trong mã nguồn, coi enum { A = 1, B = 2 }- là A|B"hợp pháp" từ logic chương trình Góc nhìn cá nhân?)
    • Tên kiểu của enum có thể xuất hiện ở nhiều nơi trong RTTI, thông điệp của trình biên dịch, v.v. - có thể hữu ích, có thể gây khó chịu
    • bạn không thể sử dụng phép liệt kê mà không có đơn vị dịch thực sự nhìn thấy giá trị, điều đó có nghĩa là các enum trong API thư viện cần các giá trị được hiển thị trong tiêu đề makevà các công cụ biên dịch dựa trên dấu thời gian khác sẽ kích hoạt tính năng biên dịch lại của máy khách khi chúng bị thay đổi (xấu! )

  1. constS:

    • các vấn đề xung đột phạm vi / định danh đúng được xử lý độc đáo
    • loại mạnh, đơn, do người dùng chỉ định
      • bạn có thể cố gắng "gõ" một #defineala #define S std::string("abc"), nhưng việc liên tục tránh việc xây dựng lặp đi lặp lại các thời gian riêng biệt tại mỗi điểm sử dụng
    • Biến chứng quy tắc một định nghĩa
    • có thể lấy địa chỉ, tạo tham chiếu const cho họ, v.v.
    • tương tự nhất với constgiá trị không , giúp giảm thiểu công việc và tác động nếu chuyển đổi giữa hai giá trị
    • giá trị có thể được đặt bên trong tệp triển khai, cho phép biên dịch lại cục bộ và chỉ các liên kết máy khách để nhận thay đổi

  1. #defineS:

    • Phạm vi "toàn cầu" / dễ bị xung đột hơn, có thể tạo ra các vấn đề biên dịch khó giải quyết và kết quả thời gian chạy không mong muốn thay vì các thông báo lỗi nghiêm trọng; giảm thiểu điều này đòi hỏi:
      • các định danh dài, tối nghĩa và / hoặc phối hợp tập trung, và truy cập vào chúng không thể hưởng lợi từ việc kết hợp hoàn toàn không gian tên được sử dụng / hiện tại / Koenig, các bí danh không gian tên, v.v.
      • trong khi thực hành tốt nhất cho phép các mã định danh tham số mẫu là các chữ cái in hoa một ký tự (có thể theo sau là một số), việc sử dụng các mã định danh khác không có chữ thường được dành riêng và dự kiến ​​cho định nghĩa tiền xử lý (bên ngoài thư viện OS và C / C ++ tiêu đề). Điều này rất quan trọng đối với việc sử dụng quy trình tiền xử lý ở quy mô doanh nghiệp để có thể quản lý được. Thư viện bên thứ 3 có thể được mong đợi tuân thủ. Quan sát điều này ngụ ý việc di chuyển các hằng số hoặc enum hiện có đến / từ định nghĩa liên quan đến thay đổi viết hoa và do đó yêu cầu chỉnh sửa mã nguồn của máy khách thay vì biên dịch lại "đơn giản". (Cá nhân, tôi viết hoa chữ cái đầu tiên của bảng liệt kê nhưng không phải là consts, vì vậy tôi cũng sẽ bị di chuyển giữa hai thứ đó - có lẽ đã đến lúc phải suy nghĩ lại về điều đó.)
    • có thể thực hiện nhiều thao tác thời gian biên dịch hơn: nối chuỗi theo nghĩa đen, xâu chuỗi (lấy kích thước của chúng), nối vào các định danh
      • Nhược điểm là được đưa ra #define X "x"và một số sử dụng máy khách ala "pre" X "post", nếu bạn muốn hoặc cần biến X thành biến có thể thay đổi thời gian chạy thay vì hằng số bạn buộc chỉnh sửa mã máy khách (thay vì chỉ biên dịch lại), trong khi quá trình chuyển đổi dễ dàng hơn từ một const char*hoặc const std::stringcho chúng đã buộc người dùng phải kết hợp chặt chẽ các hoạt động nối (ví dụ như "pre" + X + "post"cho string)
    • không thể sử dụng sizeoftrực tiếp trên một chữ số được xác định
    • tháo gỡ (GCC không cảnh báo nếu so với unsigned)
    • một số chuỗi trình biên dịch / trình liên kết / trình gỡ lỗi có thể không hiển thị mã định danh, vì vậy bạn sẽ được giảm xuống khi xem "số ma thuật" (chuỗi, bất cứ điều gì ...)
    • không thể lấy địa chỉ
    • giá trị thay thế không cần phải hợp pháp (hoặc rời rạc) trong bối cảnh #define được tạo, vì nó được đánh giá tại mỗi điểm sử dụng, do đó bạn có thể tham chiếu các đối tượng chưa được khai báo, tùy thuộc vào "triển khai" mà không cần được bao gồm trước, tạo "hằng" như { 1, 2 }có thể được sử dụng để khởi tạo mảng, hoặc #define MICROSECONDS *1E-6vv ( chắc chắn không khuyến nghị điều này!)
    • một số điều đặc biệt như __FILE____LINE__có thể được kết hợp vào sự thay thế vĩ mô
    • bạn có thể kiểm tra sự tồn tại và giá trị trong các #ifcâu lệnh cho điều kiện bao gồm mã (mạnh hơn so với tiền xử lý "nếu" vì mã không cần phải biên dịch được nếu không được bộ tiền xử lý chọn), sử dụng #undef-ine, xác định lại, v.v.
    • văn bản thay thế phải được tiếp xúc:
      • trong đơn vị dịch thuật, nó được sử dụng bởi điều đó có nghĩa là các macro trong thư viện cho khách hàng sử dụng phải nằm trong tiêu đề, makevà các công cụ biên dịch lại dựa trên dấu thời gian khác sẽ kích hoạt biên dịch lại ứng dụng khách khi chúng bị thay đổi (xấu!)
      • hoặc trên dòng lệnh, nơi cần được chăm sóc nhiều hơn để đảm bảo mã máy khách được biên dịch lại (ví dụ: Makefile hoặc tập lệnh cung cấp định nghĩa nên được liệt kê dưới dạng phụ thuộc)

Quan điểm cá nhân của tôi:

Theo nguyên tắc chung, tôi sử dụng consts và coi chúng là tùy chọn chuyên nghiệp nhất cho việc sử dụng chung (mặc dù những cái khác có một sự đơn giản lôi cuốn người lập trình lười biếng cũ này).


1
Câu trả lời tuyệt vời. Một nit nhỏ: đôi khi tôi sử dụng các enum cục bộ hoàn toàn không có trong tiêu đề chỉ để rõ ràng về mã, như trong các máy trạng thái nhỏ, v.v. Vì vậy, họ không cần phải ở trong tiêu đề mọi lúc.
kert

Các pro và nhược điểm được trộn lẫn, tôi sẽ rất muốn xem một bảng so sánh.
Unknown123

@ Unknown123: vui lòng đăng một bài - Tôi không phiền nếu bạn gạt bỏ bất kỳ điểm nào bạn cảm thấy xứng đáng từ đây. Chúc mừng
Tony Delroy

48

Nếu đây là một câu hỏi C ++ và nó đề cập đến #definenhư một câu hỏi thay thế, thì đó là về các hằng số "toàn cầu" (tức là phạm vi tệp), chứ không phải về các thành viên của lớp. Khi nói đến các hằng số như vậy trong C ++ static constlà dư thừa. Trong C ++ constcó liên kết nội bộ theo mặc định và không có điểm nào trong việc khai báo chúng static. Vì vậy, nó thực sự là về constvs #define.

Và, cuối cùng, trong C ++ constlà thích hợp hơn. Ít nhất là vì các hằng số như vậy được gõ và phạm vi. Đơn giản là không có lý do để thích #definehơn const, ngoại trừ một vài ngoại lệ.

Các hằng chuỗi, BTW, là một ví dụ về trường hợp ngoại lệ như vậy. Với #definecác hằng chuỗi d, người ta có thể sử dụng tính năng nối thời gian biên dịch của trình biên dịch C / C ++, như trong

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

PS Một lần nữa, chỉ trong trường hợp, khi ai đó đề cập đến static constnhư là một thay thế #define, điều đó thường có nghĩa là họ đang nói về C, không phải về C ++. Tôi tự hỏi liệu câu hỏi này được gắn thẻ đúng không ...


1
" Đơn giản là không có lý do để thích #define " hơn những gì? Các biến tĩnh được định nghĩa trong một tệp tiêu đề?
tò mò

9

#define có thể dẫn đến kết quả bất ngờ:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Kết quả không chính xác:

y is 505
z is 510

Tuy nhiên, nếu bạn thay thế điều này bằng hằng số:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Nó đưa ra kết quả chính xác:

y is 505
z is 1010

Điều này là bởi vì #defineđơn giản là thay thế văn bản. Bởi vì làm điều này có thể làm rối loạn trật tự hoạt động, tôi sẽ khuyên bạn nên sử dụng một biến không đổi thay thế.


1
Tôi đã có một kết quả bất ngờ khác: ycó giá trị 5500, một kết nối cuối cùng nhỏ xvà 5.
Mã với Búa

5

Sử dụng một const tĩnh giống như sử dụng bất kỳ biến const nào khác trong mã của bạn. Điều này có nghĩa là bạn có thể theo dõi bất cứ nơi nào thông tin đến, trái ngược với #define sẽ chỉ được thay thế trong mã trong quy trình tiền biên dịch.

Bạn có thể muốn xem C ++ FAQ Lite cho câu hỏi này: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • Một const tĩnh được gõ (nó có một loại) và có thể được trình biên dịch kiểm tra tính hợp lệ, xác định lại, v.v.
  • một #define có thể được xác định lại không xác định bất cứ điều gì.

Thông thường bạn nên thích consts tĩnh. Nó không có nhược điểm. Bộ xử lý chủ yếu nên được sử dụng để biên dịch có điều kiện (và đôi khi có thể cho các trics thực sự bẩn).


3

Xác định các hằng số bằng cách sử dụng chỉ thị tiền xử lý #definekhông được khuyến cáo để áp dụng không chỉ trong C++, mà còn trong C. Các hằng số này sẽ không có loại. Ngay cả trong Cđã được đề xuất để sử dụng constcho hằng.



2

Luôn thích sử dụng các tính năng ngôn ngữ hơn một số công cụ bổ sung như tiền xử lý.

ES.31: Không sử dụng macro cho hằng hoặc "hàm"

Macro là một nguồn chính của lỗi. Macro không tuân theo các quy tắc loại và phạm vi thông thường. Macro không tuân theo các quy tắc thông thường để thông qua tranh luận. Macro đảm bảo rằng trình đọc của con người nhìn thấy một cái gì đó khác với những gì trình biên dịch nhìn thấy. Macros phức tạp xây dựng công cụ.

Từ hướng dẫn cốt lõi của C ++


0

Nếu bạn đang xác định một hằng số được chia sẻ giữa tất cả các thể hiện của lớp, hãy sử dụng const const. Nếu hằng số là cụ thể cho từng trường hợp, chỉ cần sử dụng const (nhưng lưu ý rằng tất cả các hàm tạo của lớp phải khởi tạo biến thành viên const này trong danh sách khởi tạo).

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.