статичний const vs #define


212

Чи краще використовувати static constvars, ніж #defineпрепроцесор? А може, це залежить від контексту?

Які переваги / недоліки у кожного методу?


14
Скотт Майєрс дуже красиво та ретельно висвітлює цю тему. Його пункт №2 у "Ефективному C ++ третьому виданні". Два особливих випадки (1) статичний const є кращим у межах класу для конкретних констант класу; (2) простору імен або const анонімного діапазону є кращим над #define.
Ерік

2
Я віддаю перевагу Enums. Тому що це гібрид обох. Не займає місця, якщо ви не створите змінну. Якщо ви просто хочете використовувати його як постійну, перерахування - найкращий варіант. Він має безпеку типу C / C ++ 11-й, а також ідеальну константу. #define - тип небезпечний, const займає місце, якщо компілятор не може його оптимізувати.
siddhusingh

1
Моє рішення, чи використовувати #defineабо static const(для рядків), керується аспектом ініціалізації (це не було зазначено у відповідях нижче): якщо константа використовується лише в рамках певного блоку компіляції, тоді я переходжу static const, інше використовую #define- уникаю фіаско ініціалізації статичного порядку isocpp.org/wiki/faq/ctors#static-init-order
Мартін Дворак

Якщо const, constexprабо enumбудь-яка варіація працює у вашому випадку, тоді віддайте перевагу цьому#define
Phil1970,

@MartinDvorak " уникайте фіаско ініціалізації статичного порядку " Як це проблема для констант?
curiousguy

Відповіді:


139

Особисто я ненавиджу препроцесора, тому завжди ходив би з цим const.

Основна перевага a #defineполягає в тому, що він не потребує пам'яті для зберігання у вашій програмі, оскільки це насправді просто заміна деякого тексту буквальним значенням. Він також має перевагу в тому, що він не має типу, тому його можна використовувати для будь-якого цілого значення без генерації попереджень.

Переваги " const" s полягають у тому, що вони можуть бути визначені, і вони можуть бути використані в ситуаціях, коли потрібно передати вказівник на об'єкт.

Я не знаю точно, до чого ти стикаєшся з " static" частиною. Якщо ви декларуєте глобально, я б розмістив його в анонімному просторі імен замість того, щоб використовувати static. Наприклад

namespace {
   unsigned const seconds_per_minute = 60;
};

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

8
Строкові константи конкретно є одними з тих, яким може бути користь від #defined, принаймні, якщо вони можуть бути використані як "будівельні блоки" для більших констант рядків. Дивіться мою відповідь для прикладу.
ANT

62
#defineПеревага не використовується який - або пам'яті неточно. «60» на прикладі повинен бути де - то зберігати, незалежно від того , це static constчи #define. Насправді я бачив компілятори, де використання #define спричиняло масове (лише для читання) споживання пам’яті, а статичний const використовував не потрібну пам'ять.
Гілад Наор

3
#Define - це як якщо б ви ввели його, тому його точно не з пам'яті.
Преподобний

27
@theReverend Чи буквальні значення якось звільнені від споживання машинних ресурсів? Ні, вони можуть використовувати їх різними способами, можливо, це не з’явиться на стеці чи купі, але в якийсь момент програма завантажується в пам'ять разом із усіма значеннями, зібраними в неї.
Sqeaky

13
@ gilad-naor, ти маєш рацію в цілому, але малі цілі числа, такі як 60, іноді можуть бути часом частковим винятком. Деякі набори інструкцій мають можливість кодувати цілі числа або підмножину цілих чисел безпосередньо в потоці інструкцій. Наприклад, MIP додають негайно ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). У такому випадку можна сказати, що #defined ціле число не використовує місця, оскільки в складеному двійковому файлі воно займає кілька запасних бітів в інструкціях, які все-таки мали існувати.
ahcox

242

Плюси і мінуси між #defines, consts і (що ви забули) enums, залежно від використання:

  1. enums:

    • можливий лише для цілих значень
    • Проблеми зіткнення з належним діапазоном / ідентифікатором добре вирішуються, особливо в класах переліку C ++ 11, де перерахування enum class Xне розбиваються на область застосуванняX::
    • сильно набраний, але до досить великого розміру int з підписом або без підпису, над яким у вас немає контролю в C ++ 03 (хоча ви можете вказати бітове поле, в яке вони повинні бути упаковані, якщо enum є членом struct / клас / союз), тоді як C ++ 11 за замовчуванням, intале може бути явно встановлений програмістом
    • не може взяти адресу - не існує жодної, оскільки значення перерахування фактично замінені рядками в точках використання
    • сильніші обмеження на використання (наприклад, приріст - template <typename T> void f(T t) { cout << ++t; }не компілюється, хоча ви можете перетворити перерахунок у клас із неявним конструктором, оператором кастингу та визначеними користувачем операторами)
    • тип кожної константи, узятий із додаючого перерахунку, тому template <typename T> void f(T)отримують чітке примірник, коли передають одне і те ж числове значення від різних перерахунків, всі вони відрізняються від будь-яких фактичних даних f(int). Код об'єкта кожної функції може бути ідентичним (ігноруючи зміщення адреси), але я б не очікував, що компілятор / посилання видалять непотрібні копії, хоча ви можете перевірити компілятор / компонувальник, якщо вам це важливо.
    • навіть при typeof / decltype не можна очікувати, що числові_ліми дадуть корисну інформацію про набір значущих значень та комбінацій (дійсно, "легальні" комбінації навіть не відзначаються у вихідному коді, врахуйте enum { A = 1, B = 2 }- це A|B"законно" з логіки програми перспектива?)
    • ім'я типу enum може з'являтися в різних місцях у RTTI, повідомленнях компілятора тощо - можливо, корисно, можливо, затуманення
    • ви не можете використовувати перерахування, якщо блок перекладу фактично не бачить значення, а значить, перерахунки в API бібліотеки потребують значень, викритих у заголовку, makeа інші інструменти рекомпіляції на основі часових позначок запускають рекомпіляцію клієнта, коли вони змінені (погано! )

  1. consts:

    • Правильно вирішено проблеми зіткнення / визначення ідентифікатора
    • сильний, єдиний, визначений користувачем тип
      • ви можете спробувати "набрати" #defineала #define S std::string("abc"), але константа уникає повторної побудови різних часових тем у кожній точці використання
    • Ускладнення правила одного визначення
    • може приймати адресу, створювати const посилання на них тощо.
    • найбільш схожий на нецінність const, яка мінімізує роботу та вплив при перемиканні між ними
    • Значення може бути розміщене всередині файлу реалізації, що дозволяє локалізованій рекомпіляції та просто клієнтським посиланням отримати зміни

  1. #defines:

    • "глобальний" сфера / більше схильний до конфліктних звичаїв, що може спричинити важкі для вирішення проблеми компіляції та несподівані результати виконання, а не здорові повідомлення про помилки; Пом'якшення цього потребує:
      • довгі, незрозумілі та / або централізовано узгоджені ідентифікатори, і доступ до них не може скористатись неявним узгодженням використовуваного / поточного / проглянутого Конігом простору імен, псевдонімів простору імен тощо.
      • в той час, як найкраща практика тріумфування дозволяє ідентифікаторам параметрів шаблону бути односимвольними великими літерами (можливо, з наступним числом), інше використання ідентифікаторів без малих літер умовно зарезервовано для очікувань визначених препроцесором (за межами бібліотеки ОС та C / C ++) заголовки). Це важливо, щоб використання препроцесора масштабу підприємства залишалося керованим. Очікується, що бібліотеки третіх сторін будуть відповідати. Спостереження за цим означає, що міграція існуючих consts або перерахунків на / з визначення визначає зміну капіталізації, а значить, вимагає редагування вихідного коду клієнта, а не "простої" перекомпіляції. (Особисто я використовую великі літери перших літерних перелічень, але не зволікань, тому мене теж би вразило міграція між цими двома - можливо, час переосмислити це.)
    • більше можливих операцій компіляції: можливе з'єднання рядків, строфікація (прийняття їх розміру), конкатенація в ідентифікатори
      • Недоліком є ​​те, що дано #define X "x"і деяке використання клієнтом ала "pre" X "post", якщо ви хочете або потрібно зробити X змінною, що змінюється часом виконання, а не константою, ви змушуєте редагувати код клієнта (а не просто рекомпіляцію), тоді як цей перехід простіший з const char*або для const std::stringних вже змусити користувача включити операції з’єднання (наприклад, "pre" + X + "post"для string)
    • не може використовуватись sizeofбезпосередньо на визначеному числовому літералі
    • нетипізований (GCC не попереджає порівняно з unsigned)
    • деякі ланцюги компілятора / linker / налагоджувача можуть не представити ідентифікатор, тому ви зведетесь до перегляду "магічних чисел" (рядки, що б не було ...)
    • не можу взяти адресу
    • заміщене значення не повинно бути законним (або дискретним) у контексті, де створюється #define, оскільки воно оцінюється в кожній точці використання, тому ви можете посилатися на ще не оголошені об'єкти, залежати від "реалізації", які не потребують заздалегідь включіть, створіть "константи", такі, { 1, 2 }які можна використовувати для ініціалізації масивів #define MICROSECONDS *1E-6тощо, ( безумовно, не рекомендуючи цього!)
    • деякі особливості, такі як __FILE__і __LINE__можуть бути включені до макрозаміни
    • ви можете перевірити наявність та значення в #ifоператорах на умовно включення коду (більш потужне, ніж після попередньої обробки "якщо", оскільки код не потрібно компілювати, якщо його не вибрав препроцесор), використовуйте #undef-ine, переосмислити і т.д.
    • заміщений текст повинен бути викритий:
      • в блоці перекладу, яким він користується, а це означає, що макроси в бібліотеках для використання клієнта повинні бути в заголовку, тому makeінші інструменти рекомпіляції на основі часових позначок запускають рекомпіляцію клієнта, коли вони змінені (погано!)
      • або в командному рядку, де потрібно ще більше уваги, щоб переконати перекомпіляцію клієнтського коду (наприклад, Makefile або скрипт, що надає визначення, повинні бути вказані як залежність)

Моя особиста думка:

Як правило, я використовую consts і вважаю їх найбільш професійним варіантом для загального користування (хоча інші мають простоту, що приваблює цього старого ледачого програміста).


1
Дивовижна відповідь. Один невеликий ніт: я іноді використовую локальні переліки, які взагалі відсутні в заголовках просто для ясності коду, як у маленьких державних машинах тощо. Тому вони не повинні бути в заголовках у будь-який час.
kert

Про і проти мішаються, я б дуже хотів побачити таблицю порівняння.
Невідомо123,

@ Unknown123: не соромтеся опублікувати одне - я не заперечую, якщо ви вирвете звідси будь-які моменти, які вважаєте себе гідними. Ура
Тоні Делрой

48

Якщо це питання C ++ і воно згадується #defineяк альтернатива, то мова йде про "глобальні" (тобто файлові) константи, а не про членів класу. Що стосується таких констант, то в С ++ static constзайве. У C ++ constза замовчуванням є внутрішні зв’язки, і немає сенсу їх оголошувати static. Так що це насправді про constVS. #define.

І, нарешті, в С ++ constє кращим. Принаймні тому, що такі константи набираються та розміщуються. Там просто немає причин віддавати перевагу #defineбільш const, крім кількох винятків.

Строкові константи - BTW - один із прикладів такого винятку. З #defineконстантами d string можна використовувати функцію конкатенації часу компіляції компіляторів C / C ++, як в

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

Знову ж таки, про всяк випадок, коли хтось згадує static constяк альтернативу #define, це зазвичай означає, що вони говорять про C, а не про C ++. Цікаво, чи правильно позначено це питання ...


1
" просто немає причин віддавати перевагу #define " Над чим? Статичні змінні, визначені у файлі заголовка?
curiousguy

9

#define може призвести до несподіваних результатів:

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

Виводить неправильний результат:

y is 505
z is 510

Однак якщо замінити це константами:

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

Він дає правильний результат:

y is 505
z is 1010

Це тому, що #defineпросто замінює текст. Оскільки це може серйозно зіпсувати порядок операцій, я б рекомендував замість цього використовувати постійну змінну.


1
Я мав інший несподіваний результат: yмав значення 5500, малоконечне конкатенацію xі 5.
Коди з Hammer

5

Використання статичного const - це як використання будь-яких інших змінних const у вашому коді. Це означає, що ви можете простежити, звідки походить інформація, на відміну від #define, який просто буде замінено в коді в процесі попередньої компіляції.

Ви можете поглянути на C ++ FAQ Lite для цього питання: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • Статичний const набирається (він має тип) і може перевіряти компілятором на валідність, переосмислення тощо.
  • #define можна переосмислити, не визначивши що завгодно.

Зазвичай слід віддати перевагу статичним контрастам. Це не має недоліків. Принцип роботи прпроцесора слід використовувати в основному для умовної компіляції (а іноді і для дійсно брудних трійків).


3

Визначення констант за допомогою директиви препроцесора #defineне рекомендується застосовувати не тільки в C++, але і в C. Ці константи не матимуть типу. Ще в Росії Cпропонувалося використовувати constдля констант.



2

Завжди вважайте за краще використовувати мовні функції над деякими додатковими інструментами, такими як препроцесор.

ES.31: Не використовуйте макроси для констант або "функцій"

Макроси є основним джерелом помилок. Макроси не дотримуються звичайних правил щодо сфери застосування та типу. Макроси не підкоряються звичайним правилам передачі аргументів. Макроси гарантують, що людський читач бачить щось інше, ніж те, що бачить компілятор. Макроси ускладнюють побудову інструментів.

Із основних настанов C ++


0

Якщо ви визначаєте константу, яку потрібно ділити між усіма екземплярами класу, використовуйте статичний const. Якщо константа характерна для кожного екземпляра, просто використовуйте const (але зауважте, що всі конструктори класу повинні ініціалізувати цю змінну члена const у списку ініціалізації).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.