Где мне лучше использовать макросы, а где 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