Де мені краще віддати перевагу використанню макросів, а де - 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
).
Якщо вам потрібна константа, яка може бути зрозумілою препроцесору, тоді вам слід використовувати препроцесор для її визначення. Якщо вам потрібна константа для звичайного коду С ++, використовуйте звичайний код С ++.
Наведений вище приклад - лише для демонстрації стану препроцесора, але навіть цей код може уникнути використання препроцесора:
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 проти std::max
). Крім того, макрос, який працює, може бути легко використаний по-іншому, що потім може викликати дивні помилки компіляції. (наприклад, Q_PROPERTY
використовується для членів структури)
Через усі ці невизначеності, добре уникати стилю коду, щоб уникати макросів, точно так само, як зазвичай уникати gotos.
constexpr
є семантично визначеним, і, отже, зазвичай породжує набагато менше проблем.
#if
речей, для яких препроцесор насправді корисний. Визначення константи - не одна з речей, для якої корисний препроцесор, якщо ця константа не повинна бути макросом, оскільки вона використовується в умовах препроцесора #if
. Якщо константа призначена для використання в звичайному коді С ++ (не директивах препроцесора), тоді використовуйте звичайну змінну С ++, а не макрос препроцесора.
Чудова відповідь Джонатана Уейклі . Я б також порадив вам поглянути на відповідь 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