Альтернативы использованию "#define " в C++? Почему на это смотрят неодобрительно?
Я разрабатываю C++ менее года, но за это время я слышал, как несколько человек говорят о том, как ужасно #define
есть. Теперь я понимаю, что он интерпретируется препроцессором вместо компилятора и, следовательно, не может быть отлажен, но действительно ли это так плохо?
вот пример (непроверенный код, но вы получаете общую идею):
#define VERSION "1.2"
#include <string>
class Foo {
public:
string getVersion() {return "The current version is "+VERSION;}
};
- почему этот код плохо?
- есть ли альтернатива использованию
#define
?
5 ответов
почему этот код плохо?
потому что версия может быть перезаписана, и компилятор не скажет вам.
есть ли альтернатива использованию #define?
const char * VERSION = "1.2";
или
const std::string VERSION = "1.2";
реальная проблема заключается в том, что определения обрабатываются другим инструментом из остальной части языка (препроцессор). Как следствие, компилятор не знает об этом и не может помочь вам, когда что – то идет не так-например, повторное использование имени препроцессора.
рассмотрим случай max
который иногда реализуется как макрос. Как следствие, вы не можете использовать идентификатор max
в любом месте в коде. в любом месте. Но компилятор вам не скажет. Вместо этого ваш код будет ужасно неправильным, и вы понятия не имеете, почему.
теперь, с некоторой осторожностью эта проблема может быть сведена к минимуму (если не полностью устранена). Но для большинства применений #define
в любом случае есть лучшие альтернативы, Поэтому расчет затрат/выгод становится асимметричным: небольшой недостаток для нет никакой выгоды. Зачем использовать дефектную функцию, если она не дает никаких преимуществ?
так вот очень простая схема:
- нужен константа? Используйте константу (не define)
- нужна функция? Используйте функцию (не define)
- нужно что-то, что нельзя смоделировать с помощью константы или функции? Используйте define, но делайте это правильно.
делать это "правильно" - само по себе искусство, но есть несколько простых рекомендаций:
используйте уникальное имя. Все заглавные, всегда с префиксом уникального идентификатора библиотеки.
max
? Из.VERSION
? Из. Вместо этого используйтеMY_COOL_LIBRARY_MAX
иMY_COOL_LIBRARY_VERSION
. Например, библиотеки Boost, большие пользователи макросов, всегда используют макросы, начиная сBOOST_<LIBRARY_NAME>_
.-
остерегайтесь оценки. По сути, параметр в макро-это просто текст, который заменяется. Как следствие,
#define MY_LIB_MULTIPLY(x) x * x
сломано: его можно использовать какMY_LIB_MULTIPLY(2 + 5)
, в результате2 + 5 * 2 + 5
. Не то, что мы хотели. Чтобы защититься от этого, всегда parenhesise все использование аргументов (если вы не знаете ровно то, что вы делаете – спойлер: вы, вероятно, нет; даже эксперты получают это неправильно тревожно часто).правильной версией этого макроса будет:
#define MY_LIB_MULTIPLY(x) ((x) * (x))
но есть еще множество способов получить макросы ужасно неправильно, и, повторяю, компилятор не поможет вам здесь.
#define
по сути не плохо, это просто легко злоупотреблять. Для чего-то вроде строки версии он работает нормально, хотя const char*
было бы лучше, но многие программисты используют его для гораздо большего. Используя #define
Как typedef, например, глупо, когда в большинстве случаев typedef был бы лучше. Так что нет ничего плохого в #define
заявления, и некоторые вещи не могут быть сделаны без них. Они должны оцениваться на индивидуальной основе. Если вы можете найти способ решить проблему не используя препроцессор, вы должны это сделать.
Я бы не использовать #define
чтобы определить постоянное использование static
ключевое слово или еще лучше
const int kMajorVer = 1;
const int kMinorVer = 2;
ИЛИ
const std::string kVersion = "1.2";
Herb Саттер имеет отличную статью здесь подробно почему #define
плохо и перечисляет некоторые примеры, где действительно нет другого способа достичь того же самого:http://www.gotw.ca/gotw/032.htm.
в основном, как со многими вещами, его штраф, если вы используете его правильно, но легко злоупотреблять и ошибки макроса особенно загадочный и жук для отладки.
Я лично использую их для условного кода отладки, а также для представления данных вариантов, которое подробно описано в конце статьи Саттера.
в целом препроцессор плох, потому что он создает небезопасный процесс компиляции с двумя проходами, создает трудности для декодирования сообщений об ошибках и может привести к трудночитаемому коду. Вы не должны использовать его, если это возможно:
const char* VERSION = "1.2"
однако есть случаи, когда невозможно сделать то, что вы хотите сделать без препроцессора:
#define Log(x) cout << #x << " = " << (x) << endl;