Где мне лучше использовать макросы, а где constexpr ? Разве они не одинаковы?
#define MAX_HEIGHT 720
против
constexpr unsigned int max_height = 720;
Где мне лучше использовать макросы, а где constexpr ? Разве они не одинаковы?
#define MAX_HEIGHT 720
против
constexpr unsigned int max_height = 720;
Ответы:
Разве они не одинаковы?
Нет. Абсолютно нет. Даже не близко.
Помимо того факта, что ваш макрос - это, int
а ваш constexpr unsigned
- есть unsigned
, существуют важные различия, и у макросов есть только одно преимущество.
Макрос определяется препроцессором и просто подставляется в код каждый раз, когда он возникает. Препроцессор тупой и не понимает синтаксиса или семантики C ++. Макросы игнорируют такие области, как пространства имен, классы или функциональные блоки, поэтому вы не можете использовать имя для чего-либо еще в исходном файле. Это неверно для константы, определенной как правильная переменная C ++:
#define MAX_HEIGHT 720
constexpr int max_height = 720;
class Window {
// ...
int max_height;
};
Это нормально, когда переменная- max_height
член вызывается, потому что она является членом класса, поэтому имеет другую область видимости и отличается от той, которая находится в области пространства имен. Если вы попытаетесь повторно использовать имя MAX_HEIGHT
для члена, то препроцессор изменит его на эту ерунду, которая не будет компилироваться:
class Window {
// ...
int 720;
};
Вот почему вы должны давать макросы, UGLY_SHOUTY_NAMES
чтобы они выделялись, и вы можете осторожно присваивать им имена, чтобы избежать конфликтов. Если вы не используете макросы без надобности, вам не о чем беспокоиться (и не нужно читать SHOUTY_NAMES
).
Если вам просто нужна константа внутри функции, вы не можете сделать это с помощью макроса, потому что препроцессор не знает, что такое функция и что значит находиться внутри нее. Чтобы ограничить макрос только определенной частью файла, вам нужно #undef
снова:
int limit(int height) {
#define MAX_HEIGHT 720
return std::max(height, MAX_HEIGHT);
#undef MAX_HEIGHT
}
Сравните с гораздо более разумным:
int limit(int height) {
constexpr int max_height = 720;
return std::max(height, max_height);
}
Почему вы предпочли бы макрос?
Переменная constexpr - это переменная, поэтому она действительно существует в программе, и вы можете делать обычные вещи C ++, например брать ее адрес и связывать с ним ссылку.
Этот код имеет неопределенное поведение:
#define MAX_HEIGHT 720
int limit(int height) {
const int& h = std::max(height, MAX_HEIGHT);
// ...
return h;
}
Проблема в том, что MAX_HEIGHT
это не переменная, поэтому для вызова std::max
временного int
должен быть создан компилятором. Ссылка, которую возвращает, std::max
может затем относиться к тому временному объекту, который не существует после конца этого оператора, поэтому return h
обращается к недопустимой памяти.
Этой проблемы просто не существует с правильной переменной, потому что у нее есть фиксированное место в памяти, которое не исчезает:
int limit(int height) {
constexpr int max_height = 720;
const int& h = std::max(height, max_height);
// ...
return h;
}
(На практике вы, вероятно, заявили бы, что int h
нет, const int& h
но проблема может возникнуть в более тонких контекстах.)
Единственный раз, когда нужно предпочесть макрос, - это когда вам нужно, чтобы его значение было понятно препроцессору для использования в #if
условиях, например
#define MAX_HEIGHT 720
#if MAX_HEIGHT < 256
using height_type = unsigned char;
#else
using height_type = unsigned int;
#endif
Вы не можете использовать здесь переменную, потому что препроцессор не понимает, как обращаться к переменным по имени. Он понимает только базовые вещи, такие как расширение макросов и директивы, начинающиеся с #
(вроде #include
и #define
и #if
).
Если вам нужна константа, которую может понять препроцессор, вы должны использовать препроцессор для ее определения. Если вам нужна константа для обычного кода C ++, используйте обычный код C ++.
Приведенный выше пример просто демонстрирует условие препроцессора, но даже этот код может избежать использования препроцессора:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
constexpr
Потребность переменная не не занимают память , пока его адрес (указатель / ссылка) принимается; в противном случае его можно полностью оптимизировать (и я думаю, что это может быть Standardese). Я хочу подчеркнуть это, чтобы люди не продолжали использовать старый, низший уровень « enum
взлома», исходя из ошибочной идеи, что тривиальный элемент constexpr
, не требующий хранилища, тем не менее займет кое-что.
int height
была бы такая же проблема, как и с макросом, поскольку его область действия привязана к функции, по сути, тоже временно. 3. Приведенный выше комментарий «const int & h продлит время жизни временного» верен.
limit
, проблема в возвращаемом значении std::max
. 2. да, поэтому не возвращает ссылку. 3. Неправильно, см. Ссылку coliru выше.
const int& h = max(x, y);
и max
возвращается по значению, время жизни его возвращаемого значения увеличивается. Не по возвращаемому типу, а по тому, к const int&
чему он привязан. Я правильно написал.
Вообще говоря, вы должны использовать constexpr
всякий раз, когда можете, и макросы, только если нет другого решения.
Макросы - это простая замена в коде, и по этой причине они часто вызывают конфликты (например, max
макрос windows.h vs std::max
). Кроме того, работающий макрос можно легко использовать по-другому, что может вызвать странные ошибки компиляции. (например, Q_PROPERTY
используется на элементах конструкции)
Из-за всех этих неопределенностей рекомендуется избегать макросов в хорошем стиле кода, точно так же, как вы обычно избегаете gotos.
constexpr
определяется семантически и поэтому обычно вызывает гораздо меньше проблем.
#if
чего препроцессор действительно полезен. Определение константы - не то, для чего препроцессор полезен, если только эта константа не должна быть макросом, потому что она используется в условиях препроцессора #if
. Если константа предназначена для использования в обычном коде C ++ (а не в директивах препроцессора), используйте обычную переменную C ++, а не макрос препроцессора.
Отличный ответ Джонатона Уэйкли . Я также советую вам взглянуть на ответ jogojapan относительно того, в чем разница, const
и constexpr
прежде чем вы даже начнете рассматривать использование макросов.
Макросы тупые, но в хорошем смысле. Якобы в настоящее время они помогают при сборке, когда вы хотите, чтобы очень конкретные части вашего кода компилировались только при наличии определенных параметров сборки. Как правило, все , что средства принимают ваше имя макроса, или еще лучше, давайте назовем это Trigger
, и добавляющие вещи , как, /D:Trigger
, -DTrigger
и т.д. , для автоматической сборки используются.
Хотя существует множество различных применений макросов, я чаще всего вижу два неплохих / устаревших метода:
Таким образом, хотя вы можете в случае OP достичь той же цели по определению int с помощью constexpr
или a MACRO
, маловероятно, что они будут пересекаться при использовании современных соглашений. Вот несколько распространенных макросов, которые еще не были прекращены.
#if defined VERBOSE || defined DEBUG || defined MSG_ALL
// Verbose message-handling code here
#endif
В качестве другого примера использования макросов предположим, что у вас есть готовое к выпуску оборудование или, возможно, его конкретное поколение, которое имеет некоторые хитрые обходные пути, которые другим не требуются. Мы определим этот макрос как GEN_3_HW
.
#if defined GEN_3_HW && defined _WIN64
// Windows-only special handling for 64-bit upcoming hardware
#elif defined GEN_3_HW && defined __APPLE__
// Special handling for macs on the new hardware
#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__
// Greetings, Outlander! ;)
#else
// Generic handling
#endif