Помимо того факта, что ваш макрос - это, intа ваш constexpr unsigned- есть unsigned, существуют важные различия, и у макросов есть только одно преимущество.
Объем
Макрос определяется препроцессором и просто подставляется в код каждый раз, когда он возникает. Препроцессор тупой и не понимает синтаксиса или семантики C ++. Макросы игнорируют такие области, как пространства имен, классы или функциональные блоки, поэтому вы не можете использовать имя для чего-либо еще в исходном файле. Это неверно для константы, определенной как правильная переменная C ++:
Это нормально, когда переменная- max_heightчлен вызывается, потому что она является членом класса, поэтому имеет другую область видимости и отличается от той, которая находится в области пространства имен. Если вы попытаетесь повторно использовать имя MAX_HEIGHTдля члена, то препроцессор изменит его на эту ерунду, которая не будет компилироваться:
classWindow {// ...int720;
};
Вот почему вы должны давать макросы, UGLY_SHOUTY_NAMESчтобы они выделялись, и вы можете осторожно присваивать им имена, чтобы избежать конфликтов. Если вы не используете макросы без надобности, вам не о чем беспокоиться (и не нужно читать SHOUTY_NAMES).
Если вам просто нужна константа внутри функции, вы не можете сделать это с помощью макроса, потому что препроцессор не знает, что такое функция и что значит находиться внутри нее. Чтобы ограничить макрос только определенной частью файла, вам нужно #undefснова:
Переменная constexpr - это переменная, поэтому она действительно существует в программе, и вы можете делать обычные вещи C ++, например брать ее адрес и связывать с ним ссылку.
Проблема в том, что MAX_HEIGHTэто не переменная, поэтому для вызова std::maxвременного intдолжен быть создан компилятором. Ссылка, которую возвращает, std::maxможет затем относиться к тому временному объекту, который не существует после конца этого оператора, поэтому return hобращается к недопустимой памяти.
Этой проблемы просто не существует с правильной переменной, потому что у нее есть фиксированное место в памяти, которое не исчезает:
(На практике вы, вероятно, заявили бы, что int hнет, const int& hно проблема может возникнуть в более тонких контекстах.)
Условия препроцессора
Единственный раз, когда нужно предпочесть макрос, - это когда вам нужно, чтобы его значение было понятно препроцессору для использования в #ifусловиях, например
Вы не можете использовать здесь переменную, потому что препроцессор не понимает, как обращаться к переменным по имени. Он понимает только базовые вещи, такие как расширение макросов и директивы, начинающиеся с #(вроде #includeи #defineи #if).
Если вам нужна константа, которую может понять препроцессор, вы должны использовать препроцессор для ее определения. Если вам нужна константа для обычного кода C ++, используйте обычный код C ++.
Приведенный выше пример просто демонстрирует условие препроцессора, но даже этот код может избежать использования препроцессора:
using height_type = std::conditional_t<max_height < 256, unsignedchar, unsignedint>;
constexprПотребность переменная не не занимают память , пока его адрес (указатель / ссылка) принимается; в противном случае его можно полностью оптимизировать (и я думаю, что это может быть Standardese). Я хочу подчеркнуть это, чтобы люди не продолжали использовать старый, низший уровень « enumвзлома», исходя из ошибочной идеи, что тривиальный элемент constexpr, не требующий хранилища, тем не менее займет кое-что.
underscore_d
3
Ваш раздел «Настоящее место в памяти» неверен: 1. Вы возвращаетесь по значению (int), поэтому копия сделана, временная не проблема. 2. Если бы вы вернулись по ссылке (int &), тогда у вас int heightбыла бы такая же проблема, как и с макросом, поскольку его область действия привязана к функции, по сути, тоже временно. 3. Приведенный выше комментарий «const int & h продлит время жизни временного» верен.
PoweredByRice
4
@underscore_d правда, но это не меняет аргумент. Переменная не требует хранения, если ее не использовать отдельно. Дело в том, что когда требуется реальная переменная с хранилищем, переменная constexpr поступает правильно.
Джонатан
1
@PoweredByRice 1. проблема не связана с возвращаемым значением limit, проблема в возвращаемом значении std::max. 2. да, поэтому не возвращает ссылку. 3. Неправильно, см. Ссылку coliru выше.
Джонатан
3
@PoweredByRice вздохнул, тебе действительно не нужно объяснять мне, как работает C ++. Если у вас есть const int& h = max(x, y);и maxвозвращается по значению, время жизни его возвращаемого значения увеличивается. Не по возвращаемому типу, а по тому, к const int&чему он привязан. Я правильно написал.
Джонатан
11
Вообще говоря, вы должны использовать constexprвсякий раз, когда можете, и макросы, только если нет другого решения.
Обоснование:
Макросы - это простая замена в коде, и по этой причине они часто вызывают конфликты (например, maxмакрос windows.h vs std::max). Кроме того, работающий макрос можно легко использовать по-другому, что может вызвать странные ошибки компиляции. (например, Q_PROPERTYиспользуется на элементах конструкции)
Из-за всех этих неопределенностей рекомендуется избегать макросов в хорошем стиле кода, точно так же, как вы обычно избегаете gotos.
constexpr определяется семантически и поэтому обычно вызывает гораздо меньше проблем.
Условная компиляция с использованием того, для #ifчего препроцессор действительно полезен. Определение константы - не то, для чего препроцессор полезен, если только эта константа не должна быть макросом, потому что она используется в условиях препроцессора #if. Если константа предназначена для использования в обычном коде C ++ (а не в директивах препроцессора), используйте обычную переменную C ++, а не макрос препроцессора.
Джонатан
За исключением использования вариативных макросов, в основном макрос используется для переключателей компилятора, но попытка заменить текущие операторы макроса (например, условные, строковые буквальные переключатели), работающие с операторами реального кода, с constexpr - хорошая идея?
Я бы сказал, что переключатели компилятора тоже не лучшая идея. Однако я полностью понимаю, что иногда это необходимо (также макросы), особенно при работе с кроссплатформенным или встроенным кодом. Чтобы ответить на ваш вопрос: если вы уже имеете дело с препроцессором, я бы использовал макросы, чтобы было понятно и интуитивно понятно, что такое препроцессор, а что время компиляции. Я бы также посоветовал подробно прокомментировать и использовать его как можно короче и локально (избегайте распространения макросов или 100 строк #if). Возможно, исключением является типичная защита #ifndef (стандартная для #pragma once), которая хорошо известна.
Макросы тупые, но в хорошем смысле. Якобы в настоящее время они помогают при сборке, когда вы хотите, чтобы очень конкретные части вашего кода компилировались только при наличии определенных параметров сборки. Как правило, все , что средства принимают ваше имя макроса, или еще лучше, давайте назовем это 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
Ответы:
Нет. Абсолютно нет. Даже не близко.
Помимо того факта, что ваш макрос - это,
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 vsstd::max
). Кроме того, работающий макрос можно легко использовать по-другому, что может вызвать странные ошибки компиляции. (например,Q_PROPERTY
используется на элементах конструкции)Из-за всех этих неопределенностей рекомендуется избегать макросов в хорошем стиле кода, точно так же, как вы обычно избегаете gotos.
constexpr
определяется семантически и поэтому обычно вызывает гораздо меньше проблем.источник
#if
чего препроцессор действительно полезен. Определение константы - не то, для чего препроцессор полезен, если только эта константа не должна быть макросом, потому что она используется в условиях препроцессора#if
. Если константа предназначена для использования в обычном коде C ++ (а не в директивах препроцессора), используйте обычную переменную C ++, а не макрос препроцессора.Отличный ответ Джонатона Уэйкли . Я также советую вам взглянуть на ответ jogojapan относительно того, в чем разница,
const
иconstexpr
прежде чем вы даже начнете рассматривать использование макросов.Макросы тупые, но в хорошем смысле. Якобы в настоящее время они помогают при сборке, когда вы хотите, чтобы очень конкретные части вашего кода компилировались только при наличии определенных параметров сборки. Как правило, все , что средства принимают ваше имя макроса, или еще лучше, давайте назовем это
Trigger
, и добавляющие вещи , как,/D:Trigger
,-DTrigger
и т.д. , для автоматической сборки используются.Хотя существует множество различных применений макросов, я чаще всего вижу два неплохих / устаревших метода:
Таким образом, хотя вы можете в случае OP достичь той же цели по определению int с помощью
constexpr
или aMACRO
, маловероятно, что они будут пересекаться при использовании современных соглашений. Вот несколько распространенных макросов, которые еще не были прекращены.#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
источник