it-swarm.com.ru

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

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

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

184
Patrice Bernassola

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

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

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

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

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}
126
T.E.D.

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

  • перечисления
    • возможно только для целочисленных значений
    • должным образом разбросанные/идентифицирующие проблемы коллизий обрабатываются хорошо, особенно в перечислимых классах C++ 11, где перечисления для enum class X устраняются неоднозначностью из области X::
    • строго типизированный, но с достаточно большим размером int со знаком или без знака, над которым у вас нет контроля в C++ 03 (хотя вы можете указать битовое поле, в которое они должны быть упакованы, если enum является членом struct/class/union), в то время как C++ 11 по умолчанию равен int, но может быть явно задан программистом
    • не могу взять адрес - его нет, так как значения перечисления эффективно подставляются в пунктах использования
    • более строгие ограничения использования (например, приращение - template <typename T> void f(T t) { cout << ++t; } не будет компилироваться, хотя вы можете заключить перечисление в класс с неявным конструктором, оператором приведения и пользовательскими операторами)
    • тип каждой константы взят из включающего перечисления, поэтому 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 ala #define S std::string("abc"), но эта константа позволяет избежать повторного создания различных временных объектов в каждой точке использования.
    • Осложнения по правилу одного определения
    • может брать адреса, создавать константные ссылки на них и т.д.
    • больше всего похоже на значение не -const, которое минимизирует работу и влияние при переключении между двумя
    • значение может быть помещено в файл реализации, позволяя локализованную перекомпиляцию и просто ссылки клиента, чтобы получить изменения
  • определяет
    • «глобальная» область действия/более склонна к конфликтным использованиям, что может приводить к трудно разрешаемым проблемам компиляции и неожиданным результатам во время выполнения, а не к нормальным сообщениям об ошибках; смягчение этого требует:
      • длинные, неясные и/или централизованно координируемые идентификаторы, и доступ к ним не может быть извлечен из неявного соответствия используемого/текущего/искомого Кенига пространства имен, псевдонимов пространства имен и т. д.
      • в то время как передовая практика допускает, что идентификаторы параметров шаблона должны быть односимвольными заглавными буквами (возможно, за которыми следует число), другое использование идентификаторов без строчных букв традиционно зарезервировано для ожидаемого определения препроцессора (вне библиотеки ОС и библиотеки C/C++) заголовки). Это важно, чтобы использование препроцессора масштаба предприятия оставалось управляемым. Ожидается, что сторонние библиотеки будут соответствовать. Наблюдение за этим подразумевает миграцию существующих констант или перечислений в/из определений, включает изменение в заглавных буквах и, следовательно, требует редактирования исходного кода клиента, а не «простой» перекомпиляции. (Лично я пишу с заглавной буквы первую букву перечислений, но не констант, поэтому мне придется переходить между этими двумя тоже - возможно, пришло время переосмыслить это.)
    • возможно больше операций времени компиляции: конкатенация строковых литералов, строковое преобразование (принимая его размер), конкатенация в идентификаторы Недостатком является то, что данный #define X "x" и некоторый клиент используют ala "pre" X "post", если вы хотите или должны сделать X изменяемой во время выполнения переменной, а не константой, которую вы принудительно редактируете в клиентском коде (а не просто перекомпилируете), тогда как этот переход проще с const char* или const std::string, поскольку они уже заставляют пользователя включать операции конкатенации (например, "pre" + X + "post" для string)
      • нетипизированный (GCC не предупреждает, если сравнивать с sizeof)
    • некоторые цепочки компилятора/компоновщика/отладчика могут не предоставлять идентификатор, поэтому вы будете вынуждены смотреть на "магические числа" (строки, что угодно ...)
    • не могу взять адрес
    • подставленное значение не обязательно должно быть допустимым (или дискретным) в контексте, где создается #define, так как оно оценивается в каждой точке использования, поэтому вы можете ссылаться на еще не объявленные объекты, в зависимости от "реализации", которая не нуждается предварительно включите, создайте «константы», такие как { 1, 2 }, которые можно использовать для инициализации массивов, или #define MICROSECONDS *1E-6 и т. д. ( определенно не рекомендую это!)
    • некоторые специальные вещи, такие как __FILE__ и __LINE__, могут быть включены в подстановку макросов
    • вы можете проверить наличие и значение в операторах #if для условного включения кода (более мощный, чем пост-препроцессор «если», поскольку код не нужно компилировать, если он не выбран препроцессором), использовать #undef- ine, переопределить и т. д.
    • замещенный текст должен быть выставлен:
    • в модуле перевода, которым он используется, это означает, что макросы в библиотеках для использования клиентом должны быть в заголовке, поэтому unsigned и другие инструменты перекомпиляции на основе меток времени будут вызывать перекомпиляцию клиента при их изменении (плохо!)
      • как правило, я использую makes и считаю их наиболее профессиональным вариантом для общего использования (хотя другие имеют простоту, привлекательную для этого старого ленивого программиста).
      • or on the command line, where even more care is needed to make sure client code is recompiled (e.g. the Makefile or script supplying the definition should be listed as a dependency)

As a general rule, I use consts and consider them the most professional option for general usage (though the others have a simplicity appealing to this old lazy programmer).

213
Tony Delroy

Если это вопрос C++ и в качестве альтернативы упоминается #define, то речь идет о «глобальных» (т. Е. File-scope) константах, а не о членах класса. Когда дело доходит до таких констант в 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;

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

41
AnT

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

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

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

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

4
RED SOFT ADAIR

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

1
Aleksey Bykov

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

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

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

1
ennuikiller

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

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

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

С Основные положения C++

0
Hitokage

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

0
snr