Static const vs #define

Лучше ли использовать static const vars, чем препроцессор #define? Или, может быть, это зависит от контекста?

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

Ответы

Ответ 1

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

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

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

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

namespace {
   unsigned const seconds_per_minute = 60;
};

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

Ответ 2

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

  • перечислений
    • возможно только для целых значений
    • проблемы с ограниченным охватом/идентификатором конфликта обрабатываются хорошо, особенно в классах перечислимого класса С++ 11, где перечисления для enum class X устраняются с помощью области X::
    • строго типизирован, но для достаточно большого размера знака signed-or-unsigned int, над которым у вас нет элемента управления в С++ 03 (хотя вы можете указать бит-поле, в которое они должны быть упакованы, если перечисление является членом struct/class/union), а С++ 11 по умолчанию - int, но может быть явно задан программистом
    • не может принимать адрес - там нет ни одного, поскольку значения перечисления эффективно заменяются inline в точках использования
    • более сильные ограничения использования (например, incrementing - 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 } - is A|B "юридический" с точки зрения программной логики?)
    • имя enum может отображаться в разных местах RTTI, сообщения компилятора и т.д. - возможно, полезно, возможно обфускация
    • вы не можете использовать перечисление без единицы перевода, фактически просматривая значение, что означает, что перечисления в библиотечных API-интерфейсах нуждаются в значениях, отображаемых в заголовке, а make и другие инструменты перекомпиляции на основе временной метки будут приводить к перекомпиляции клиента, когда они 'изменено (плохо!)
  • consts
    • проблемы с ограниченным охватом/идентификатором конфликта обрабатываются красиво
    • сильный, единственный, заданный пользователем тип
      • вы можете попытаться "набрать" #define ala #define S std::string("abc"), но константа избегает повторного построения отдельных временных рядов в каждой точке использования.
    • Устранение осложнений с одним определением.
    • может принимать адрес, создавать ссылки const и т.д.
    • наиболее похоже на значение не const, которое минимизирует работу и воздействие при переключении между двумя
    • значение может быть помещено внутри файла реализации, позволяя локализовать перекомпилировать и только клиентские ссылки для получения изменения
  • определяет
    • "глобальная" область/более склонна к конфликтующим обычаям, что может привести к сложным задачам компиляции и неожиданным результатам во время выполнения, а не к сообщениям об ошибках; Для этого необходимо:
      • длинные, неясные и/или централизованно скоординированные идентификаторы, а доступ к ним не может получить выгоду от неявного соответствия используемого/текущего/Koenig-look-up пространства имен, псевдонимов пространства имен и т.д.
      • в то время как перехват наилучшей практики позволяет символьным идентификаторам шаблонов быть односимвольными заглавными буквами (возможно, за ними следует число), другое использование идентификаторов без букв в нижнем регистре условно зарезервировано и ожидается от препроцессора (вне ОС и C/С++). Это важно для использования препроцессора в масштабе предприятия, чтобы оставаться управляемым. Возможно, будут соблюдаться библиотеки сторонних разработчиков. Наблюдение этого подразумевает, что миграция существующих констант или перечислений в/из определяет включает в себя изменение капитализации и, следовательно, требует редактирования исходного кода клиента, а не "простой" перекомпиляции. (Лично я использую первую букву перечислений, но не consts, поэтому я бы попал в миг между этими двумя - возможно, время переосмыслить это.)
    • возможны дополнительные операции компиляции: строковое литералирование конкатенации, строкование (с их размером), объединение в идентификаторы
      • Недостатком
      • является то, что при задании #define X "x" и некотором использовании клиента ala "pre" X "post", если вам нужно или нужно сделать X переменной, изменяемой во время выполнения, а не константой, вы принудительно редактируете код клиента (а не просто перекомпиляцию), тогда как этот переход проще с const char* или const std::string, поскольку они уже заставляют пользователя включать операции конкатенации (например, "pre" + X + "post" для string)
    • не может использовать sizeof непосредственно в определенном числовом литерале
    • untyped (GCC не предупреждает, если по сравнению с unsigned)
    • В некоторых цепочках компилятора/компоновщика/отладчика не может присутствовать идентификатор, поэтому вы будете уменьшены до взгляда на "магические числа" (строки, что угодно...)
    • не может принимать адрес
    • замещаемое значение не обязательно должно быть законным (или дискретным) в контексте, где создается #define, поскольку оно оценивалось в каждой точке использования, поэтому вы можете ссылаться на еще не объявленные объекты, зависят от "реализации", которые не нужно предварительно включать, создавать "константы", такие как { 1, 2 }, которые могут использоваться для инициализации массивов или #define MICROSECONDS *1E-6 и т.д. (определенно не рекомендуя это!)
    • некоторые специальные вещи, такие как __FILE__ и __LINE__, могут быть включены в макроподстановку
    • вы можете проверить существование и значение в операторах #if для условного включения кода (более мощный, чем постпреобразование "если", поскольку код не нужно компилировать, если он не выбран препроцессором), используйте #undef - ine, redefine и т.д.
    • замещенный текст должен быть раскрыт:
      • в используемом модуле трансляции, что означает, что макросы в библиотеках для использования клиентами должны быть в заголовке, поэтому make и другие инструменты перекомпиляции на основе временной метки вызовут перекомпиляцию клиента при их изменении (плохо!)
      • или в командной строке, где требуется еще больше внимания, чтобы убедиться, что клиентский код перекомпилирован (например, Makefile или script, поставляющие определение, должны быть указаны как зависимость)

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

Ответ 3

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

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

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

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

Ответ 4

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

Возможно, вы захотите взглянуть на С++ FAQ Lite для этого вопроса: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7

Ответ 5

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

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

Ответ 6

Смотрите здесь: static const vs define

обычно объявление const (обратите внимание, что оно не обязательно должно быть статическим) - это путь

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

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

От Основные принципы С++