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

212

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

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

Патрис Бернассола
источник
14
Скотт Мейерс освещает эту тему очень хорошо и тщательно. Его пункт № 2 в "Эффективном C ++ Третье издание". В двух особых случаях (1) статическая константа предпочтительна в пределах класса для констант, специфичных для класса; (2) пространство имен или анонимная область действия const предпочтительнее, чем #define.
Эрик
2
Я предпочитаю Enums. Потому что это гибрид обоих. Не занимает места, пока вы не создадите его переменную. Если вы просто хотите использовать в качестве константы, enum - лучший вариант. Он имеет безопасность типов в C / C ++ 11 std, а также идеальную константу. #define тип небезопасен, const занимает место, если компилятор не может его оптимизировать.
Сиддхусинг
1
Мое решение, использовать ли #defineили static const(для строк), зависит от аспекта инициализации (об этом не было сказано в ответах ниже): если константа используется только внутри определенного модуля компиляции, тогда я продолжаю static const, иначе я использую #define- избегайте фиаско статической инициализации порядка isocpp.org/wiki/faq/ctors#static-init-order
Мартин Дворжак
Если const, constexprили , enumили любые вариации работа в вашем случае, то предпочитает его#define
Phil1970
@MartinDvorak " фиаско во избежание статической инициализации порядка " Как это проблема для констант?
любопытный парень

Ответы:

139

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

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

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

Я не знаю точно, что вы получаете с " 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 приводило к значительному (только для чтения) потреблению памяти, а static const не использовала ненужную память.
Гилад Наор
3
#Define похож на то, как если бы вы его набрали, поэтому он определенно не исходит из памяти.
Преподобный
27
@theReverend Являются ли буквальные значения как-то освобождены от потребления машинных ресурсов? Нет, они просто могут использовать их по-разному, возможно, они не появятся в стеке или куче, но в какой-то момент программа загружается в память вместе со всеми скомпилированными в нее значениями.
Sqeaky
13
@ gilad-naor, Вы правы в целом, но маленькие целые числа, например 60, иногда могут быть своего рода частичным исключением. Некоторые наборы команд имеют возможность кодировать целые числа или подмножество целых чисел непосредственно в потоке команд. Например, MIP добавляют немедленно ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). В этом случае можно сказать, что #defined integer действительно не использует пробела, поскольку в скомпилированном двоичном файле он занимает несколько лишних битов в инструкциях, которые должны были существовать в любом случае.
АХОКС
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; }не компилируется, хотя вы можете заключить enum в класс с неявным конструктором, оператором приведения и пользовательскими операторами)
    • тип каждой константы, взятый из вмещающего перечисления, поэтому template <typename T> void f(T)получайте отчетливые экземпляры при передаче одного и того же числового значения из разных перечислений, каждый из которых отличается от любого фактического f(int)экземпляра. Код объекта каждой функции может быть идентичным (без учета смещения адресов), но я не ожидаю, что компилятор / компоновщик удалит ненужные копии, хотя вы можете проверить свой компилятор / компоновщик, если вам это нужно.
    • даже при использовании typeof / decltype нельзя ожидать, что numeric_limits предоставит полезную информацию о наборе значимых значений и комбинаций (действительно, «допустимые» комбинации даже не указаны в исходном коде, рассмотрим enum { A = 1, B = 2 }- A|B«законно» из логики программы) перспектива?)
    • Типовое имя enum может появляться в различных местах в RTTI, сообщениях компилятора и т. д. - возможно, полезно, возможно, запутывание
    • Вы не можете использовать перечисление, когда единица трансляции фактически не видит значение, что означает, что перечисления в библиотечных API нуждаются в значениях, представленных в заголовке, и make а другие инструменты перекомпиляции на основе временных меток будут вызывать перекомпиляцию клиента при их изменении (плохо! )

  1. constS:

    • Правильно определена область видимости / конфликт идентификаторов.
    • сильный, одиночный, указанный пользователем тип
      • Вы можете попытаться «напечатать» #define ala #define S std::string("abc"), но константа избегает повторного создания различных временных в каждой точке использования
    • Одиночные правила определения осложнений
    • может взять адрес, создать константные ссылки на них и т. д.
    • больше всего похоже на не- constзначение, которое сводит к минимуму работу и влияние при переключении между двумя
    • значение может быть помещено в файл реализации, позволяя локализованную перекомпиляцию и просто ссылки клиента, чтобы получить изменения

  1. #defineS:

    • «глобальная» область действия / более подвержена конфликтным использованиям, что может приводить к трудно разрешаемым проблемам компиляции и неожиданным результатам во время выполнения, а не к нормальным сообщениям об ошибках; смягчение этого требует:
      • длинные, неясные и / или централизованно координируемые идентификаторы, и доступ к ним не может быть извлечен из неявного соответствия используемого / текущего / искомого Кенига пространства имен, псевдонимов пространства имен и т. д.
      • в то время как передовая практика допускает, что идентификаторы параметров шаблона должны быть односимвольными заглавными буквами (возможно, за которыми следует число), другое использование идентификаторов без строчных букв традиционно зарезервировано для ожидаемого определения препроцессора (вне библиотеки ОС и библиотеки 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-ine, переопределить и т. д.
    • замещенный текст должен быть выставлен:
      • в модуле перевода, которым он используется, это означает, что макросы в библиотеках для использования клиентом должны быть в заголовке, поэтому makeдругие инструменты перекомпиляции на основе временных меток будут вызывать перекомпиляцию клиента при их изменении (плохо!)
      • или в командной строке, где требуется еще больше усилий, чтобы убедиться, что клиентский код перекомпилирован (например, Makefile или скрипт, предоставляющий определение, должны быть указаны как зависимость)

Мое личное мнение:

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

Тони Делрой
источник
1
Потрясающий ответ. Один маленький недостаток: я иногда использую локальные перечисления, которые вообще не находятся в заголовках просто для ясности кода, как в маленьких конечных автоматах и ​​тому подобное. Таким образом, они не должны всегда быть в заголовках.
Kert
Плюсы и минусы перепутаны, очень хотелось бы увидеть сравнительную таблицу.
Неизвестно123
@ Неизвестный123: не стесняйтесь писать один - я не против, если вы сдираете какие-либо пункты, которые вы считаете достойными отсюда. Приветствия
Тони Делрой
48

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

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

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

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

Juniorized
источник
1
У меня был другой неожиданный результат: yимел значение 5500, сцепление x
с
5

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

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

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

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

RED SOFT ADAIR
источник
3

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


источник
2

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

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

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

Из C ++ Core Guidelines

Hitokage
источник
0

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

ОСШ
источник