Статическая константа против #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
Помимо нескольких исключений.
Строковые константы, кстати, являются одним из примеров такого исключения. С #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++. Интересно, правильно ли помечен этот вопрос...
#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: не используйте макросы для констант или "функций"
Макросы являются основным источником ошибок. Макросы не подчиняются обычным правилам области видимости и типов. Макросы не подчиняются обычным правилам передачи аргументов. Макросы гарантируют, что читатель увидит что-то отличное от того, что видит компилятор. Макросы усложняют создание инструментов.
Если вы определяете константу, которая будет использоваться всеми экземплярами класса, используйте static const. Если константа специфична для каждого экземпляра, просто используйте const (но учтите, что все конструкторы класса должны инициализировать эту переменную-член const в списке инициализации).