Статическая константа против #define

Это лучше использовать static const чем #define препроцессор? А может это зависит от контекста?

Каковы преимущества / недостатки каждого метода?

9 ответов

Решение

Лично я ненавижу препроцессор, поэтому всегда буду использовать const.

Основным преимуществом #define является то, что он не требует памяти для хранения в вашей программе, поскольку на самом деле он просто заменяет некоторый текст буквальным значением. Он также имеет то преимущество, что у него нет типа, поэтому его можно использовать для любого целочисленного значения без генерации предупреждений.

Преимущества "const" в том, что они могут быть ограничены и могут использоваться в ситуациях, когда необходимо передать указатель на объект.

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

namespace {
   unsigned const seconds_per_minute = 60;
};

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

Плюсы и минусы ко всему, в зависимости от использования:

  • перечислений
    • возможно только для целочисленных значений
    • должным образом разбросанные / идентифицирующие проблемы коллизий обрабатываются хорошо, особенно в перечислимых классах C++11, где перечисления для enum class X неоднозначны по объему X::
    • строго типизированный, но с достаточно большим размером int со знаком или без знака, над которым у вас нет контроля в C++03 (хотя вы можете указать битовое поле, в которое они должны быть упакованы, если enum является членом struct/ класс / объединение), в то время как C++11 по умолчанию int но может быть явно задано программистом
    • не могу взять адрес - его нет, так как значения перечисления эффективно подставляются в точках использования
    • более строгие ограничения использования (например, увеличение - template <typename T> void f(T t) { cout << ++t; } не будет компилироваться, хотя вы можете заключить enum в класс с неявным конструктором, оператором приведения и пользовательскими операторами)
    • тип каждой константы взят из включающего перечисления, так template <typename T> void f(T) получить различные экземпляры при передаче одного и того же числового значения из различных перечислений, все из которых отличаются от любого фактического f(int) конкретизации. Код объекта каждой функции может быть идентичным (без учета смещения адресов), но я не ожидаю, что компилятор / компоновщик удалит ненужные копии, хотя вы можете проверить свой компилятор / компоновщик, если вам это нужно.
    • даже с typeof / decltype нельзя ожидать, что numeric_limits предоставит полезную информацию о наборе значимых значений и комбинаций (на самом деле, "допустимые" комбинации даже не указаны в исходном коде, рассмотрим enum { A = 1, B = 2 } - является A|B "законно" с точки зрения логики программы?)
    • Типовое имя enum может появляться в различных местах в RTTI, сообщениях компилятора и т. д. - возможно, полезно, возможно, запутывание
    • Вы не можете использовать перечисление, когда единица перевода не видит значение, а это означает, что перечисления в библиотечных API нуждаются в значениях, представленных в заголовке, и make и другие инструменты перекомпиляции на основе меток времени будут вызывать перекомпиляцию клиента при их изменении (плохо!)
  • consts
    • Правильно определена область видимости / конфликт идентификаторов.
    • сильный, одиночный, указанный пользователем тип
      • Вы можете попытаться "напечатать" #define ала #define S std::string("abc"), но константа избегает повторного построения различных временных в каждой точке использования
    • Осложнения по правилу одного определения
    • может взять адрес, создать константные ссылки на них и т. д.
    • наиболее похож наconst значение, которое минимизирует работу и влияние при переключении между двумя
    • значение может быть помещено в файл реализации, позволяя локализованную перекомпиляцию и просто ссылки клиента, чтобы получить изменения
  • определяет
    • "глобальная" область действия / более склонна к конфликтным использованиям, что может привести к трудно разрешаемым проблемам компиляции и неожиданным результатам во время выполнения, а не к нормальным сообщениям об ошибках; смягчение этого требует:
      • длинные, неясные и / или централизованно координируемые идентификаторы, и доступ к ним не может быть извлечен из неявного соответствия используемого / текущего / искомого Кенига пространства имен, псевдонимов пространства имен и т. д.
      • в то время как передовая практика допускает, что идентификаторы параметров шаблона должны быть односимвольными заглавными буквами (возможно, за которыми следует число), другое использование идентификаторов без строчных букв традиционно зарезервировано для ожидаемого определения препроцессора (вне библиотеки ОС и библиотеки C/C++) заголовки). Это важно, чтобы использование препроцессора масштаба предприятия оставалось управляемым. Ожидается, что сторонние библиотеки будут соответствовать. Наблюдение за этим подразумевает миграцию существующих констант или перечислений в / из определений, включает изменение в заглавных буквах и, следовательно, требует редактирования исходного кода клиента, а не "простой" перекомпиляции. (Лично я пишу с заглавной буквы первую букву перечислений, но не констант, поэтому мне придется переходить между этими двумя тоже - возможно, пришло время переосмыслить это.)
    • возможно больше операций времени компиляции: конкатенация строковых литералов, строковое преобразование (принимая его размер), конкатенация в идентификаторы
      • Недостатком является то, что дано #define X "x" и некоторое использование клиента аля "pre" X "post"Если вы хотите или должны сделать X изменяемой во время выполнения переменной, а не константой, вы принудительно редактируете код клиента (а не просто перекомпилируете), тогда как этот переход легче выполнить const char* или же const std::string учитывая, что они уже заставляют пользователя включать операции конкатенации (например, "pre" + X + "post" за string)
    • не может использовать sizeof непосредственно на определенный числовой литерал
    • нетипизированный (GCC не предупреждает по сравнению с unsigned)
    • некоторые цепочки компилятора / компоновщика / отладчика могут не предоставлять идентификатор, поэтому вы будете вынуждены смотреть на "магические числа" (строки, что угодно...)
    • не могу взять адрес
    • подставленное значение не обязательно должно быть допустимым (или дискретным) в контексте, где создается #define, поскольку оно оценивается в каждой точке использования, поэтому вы можете ссылаться на еще не объявленные объекты, в зависимости от "реализации", которая не нуждается быть предварительно включенным, создать "константы", такие как { 1, 2 } которые могут быть использованы для инициализации массивов, или #define MICROSECONDS *1E-6 и т.д. (определенно не рекомендую это!)
    • некоторые специальные вещи, такие как __FILE__ а также __LINE__ может быть включен в подстановку макросов
    • вы можете проверить на существование и ценность в #if операторы для условного включения кода (более мощный, чем пост-препроцессор "если", так как код не нужно компилировать, если он не выбран препроцессором), используйте #undef- переопределить и т. д.
    • замещенный текст должен быть выставлен:
      • в модуле перевода он используется, что означает, что макросы в библиотеках для использования клиентом должны быть в заголовке, поэтому make и другие инструменты перекомпиляции на основе меток времени будут вызывать перекомпиляцию клиента при их изменении (плохо!)
      • или в командной строке, где требуется еще больше усилий, чтобы убедиться, что клиентский код перекомпилирован (например, Makefile или скрипт, предоставляющий определение, должны быть указаны как зависимость)

Как правило, я использую constи считают их наиболее профессиональным вариантом для общего пользования (хотя другие имеют простоту, привлекательную для этого старого ленивого программиста).

Если это вопрос C++ и он упоминает #define в качестве альтернативы речь идет о "глобальных" (т. е. файловых) константах, а не о членах класса. Когда дело доходит до таких констант в C++ static const избыточно В C++ const имеют внутреннюю связь по умолчанию, и нет смысла объявлять их static, Так что это действительно о const против #define,

И, наконец, в C++ const предпочтительнее Хотя бы потому, что такие константы типизированы и ограничены. Там просто нет причин предпочитать #define над constПомимо нескольких исключений.

Строковые константы, кстати, являются одним из примеров такого исключения. С #defined строковые константы можно использовать функцию конкатенации компиляции 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;

PS Опять на всякий случай, когда кто-то упоминает static const в качестве альтернативы #defineобычно это означает, что они говорят о C, а не о C++. Интересно, правильно ли помечен этот вопрос...

#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 просто заменяет текст. Поскольку это может серьезно испортить порядок операций, я бы рекомендовал вместо этого использовать постоянную переменную.

  • Статический констант типизирован (он имеет тип) и может быть проверен компилятором на валидность, переопределение и т. Д.
  • #define может быть переопределен как неопределенный.

Обычно вы должны предпочитать статические минусы. Это не имеет недостатка. Prprocessor должен главным образом использоваться для условной компиляции (и иногда для действительно грязных уловок возможно).

Использование статического const похоже на использование любых других константных переменных в вашем коде. Это означает, что вы можете отслеживать, откуда поступает информация, в отличие от #define, который будет просто заменен в коде в процессе предварительной компиляции.

Вы можете посмотреть C++ FAQ Lite по этому вопросу: http://www.parashift.com/c++-faq-lite/newbie.html

Определение констант с помощью директивы препроцессора #define Не рекомендуется применять не только в C++но и в C, Эти константы не будут иметь тип. Даже в C было предложено использовать const для постоянных.

Как довольно старый и ржавый программист на C, который так и не смог полностью перейти на C++, потому что появились другие вещи и теперь старается справиться с Arduino, моя точка зрения проста.

#define - это директива препроцессора компилятора, и ее следует использовать как таковую для условной компиляции и т. д. Например, когда низкоуровневый код должен определять некоторые возможные альтернативные структуры данных для переносимости на конкретное оборудование. Это может привести к противоречивым результатам в зависимости от порядка компиляции и связывания ваших модулей. Если вам нужно что-то глобальное по своему охвату, определите это как таковое.

const и (static const) всегда следует использовать для именования статических значений или строк. Они типизированы и безопасны, и отладчик может полностью с ними работать.

перечисления всегда сбивали меня с толку, поэтому мне удавалось их избегать.

Пожалуйста, смотрите здесь: статическая константа против определения

как правило, декларация const (обратите внимание, что она не должна быть статичной)

Всегда предпочитайте использовать языковые функции перед некоторыми дополнительными инструментами, такими как препроцессор.

ES.31: не используйте макросы для констант или "функций"

Макросы являются основным источником ошибок. Макросы не подчиняются обычным правилам области видимости и типов. Макросы не подчиняются обычным правилам передачи аргументов. Макросы гарантируют, что читатель увидит что-то отличное от того, что видит компилятор. Макросы усложняют создание инструментов.

Из C++ Core Guidelines

Если вы определяете константу, которая будет использоваться всеми экземплярами класса, используйте static const. Если константа специфична для каждого экземпляра, просто используйте const (но учтите, что все конструкторы класса должны инициализировать эту переменную-член const в списке инициализации).

Другие вопросы по тегам