Сегодня мы посвятим время важной теме — использованию макросов в C++ с помощью директивы препроцессора #define.
Макросы в C++ представляют собой механизм текстовой подстановки, реализуемый посредством директивы #define. Они позволяют задавать именованные константы и создавать макрофункции — фрагменты кода, которые заменяются непосредственно в исходном коде на этапе препроцессинга.
Например, часто встречается макрос для задания константы:
После его определения PI можно использовать как числовую константу:
Еще одно полезное применение макросов — вычисление количества элементов в статическом массиве:
Этот макрос позволяет получить размер массива без необходимости вручную пересчитывать элементы:
Но следует помнить,
🔥 Если вы хотите углубить свои знания по этой теме, не стесняйтесь писать в комментариях!
📣 C++ Ready | #практика
Макросы в C++ представляют собой механизм текстовой подстановки, реализуемый посредством директивы #define. Они позволяют задавать именованные константы и создавать макрофункции — фрагменты кода, которые заменяются непосредственно в исходном коде на этапе препроцессинга.
Например, часто встречается макрос для задания константы:
#define PI 3.14159 // Значение числа π
После его определения PI можно использовать как числовую константу:
double r = 10;
double S = PI * r * r; // Вычисляем площадь круга с радиусом 10
Еще одно полезное применение макросов — вычисление количества элементов в статическом массиве:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
Этот макрос позволяет получить размер массива без необходимости вручную пересчитывать элементы:
int numbers[] = {1, 2, 3, 4, 5};
int size = ARRAY_SIZE(numbers); // size будет равен 5Но следует помнить,
ARRAY_SIZE() корректно работает только со статическими массивами. Если передать в него указатель (например, динамический массив), результат будет некорректным.🔥 Если вы хотите углубить свои знания по этой теме, не стесняйтесь писать в комментариях!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍10👎2
Constexpr-хеширование FNV-1a: делаем switch по строкам!
Нужен быстрый
Возьмём FNV-1a-хеш, который считается на этапе компиляции:
Теперь можно «переключаться» по строкам так же быстро, как по
🔥 Любые литеральные строки превращаются в константы, а сам
📣 C++ Ready | #практика
Нужен быстрый
switch без std::string_view сравнений? Возьмём FNV-1a-хеш, который считается на этапе компиляции:
// 32-битный constexpr-хеш FNV-1a
constexpr uint32_t fnv1a(char const* s, uint32_t h = 0x811c9dc5) {
return *s ? fnv1a(s + 1, (h ^ uint8_t(*s)) * 0x01000193) : h;
}
#define H(str) fnv1a(str)
Теперь можно «переключаться» по строкам так же быстро, как по
int:void handle(std::string_view cmd) {
switch (fnv1a(cmd.data())) {
case H("start"): /* … */ break;
case H("stop"): /* … */ break;
case H("status"): /* … */ break;
default: /* ? */ break;
}}🔥 Любые литеральные строки превращаются в константы, а сам
switch — чистый O(1) без сравнений символов.Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍10🔥4
Пишем свой assert с логированием и stacktrace!
Сейчас научимся создавать удобный макрос
Начнём с подключения библиотек из стандарта C++23 — ничего дополнительно не ставить не нужно:
Теперь объявим функцию-обёртку, которая будет бросать исключение с нужными данными:
И сам макрос, использующий эту функцию:
Давайте проверим на примере:
При запуске получим:
🔥 Таким образом ты получаешь собственный
📣 C++ Ready | #практика
Сейчас научимся создавать удобный макрос
ASSERT, который при провале проверки выводит не только выражение и место в коде, но и подробный stacktrace. Это пригодится для быстрой отладки и локализации ошибок в сложных проектах.Начнём с подключения библиотек из стандарта C++23 — ничего дополнительно не ставить не нужно:
#include <iostream>
#include <stdexcept>
#include <source_location>
#include <stacktrace>
Теперь объявим функцию-обёртку, которая будет бросать исключение с нужными данными:
void assert_fail(const char* expr,
const std::source_location& loc = std::source_location::current()) {
std::cerr << "ASSERTION FAILED: (" << expr << ")\n"
<< " at " << loc.file_name() << ":" << loc.line()
<< " in " << loc.function_name() << "\n"
<< "Stacktrace:\n" << std::stacktrace::current() << "\n";
std::terminate();
}
И сам макрос, использующий эту функцию:
#define ASSERT(expr) \
((expr) ? void(0) : assert_fail(#expr))
Давайте проверим на примере:
int factorial(int n) {
ASSERT(n >= 0);
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
std::cout << "5! = " << factorial(5) << "\n";
std::cout << "(-1)! = " << factorial(-1) << "\n"; // вызовет ASSERT
return 0;
}При запуске получим:
5! = 120
ASSERTION FAILED: (n >= 0)
at example.cpp:8 in factorial()
Stacktrace:
#0 factorial() at example.cpp:8
#1 main() at example.cpp:14
#2 __libc_start_main
...
🔥 Таким образом ты получаешь собственный
assert с детальным логом и стек-трейсом — никакой потери контекста при отладке и быстрый поиск причины крашей.Please open Telegram to view this post
VIEW IN TELEGRAM
❤18👍11🔥6🤝1
DBG-макрос на std::source_location: удобно дебажим выражения с контекстом
Иногда хочется быстро подсмотреть значение переменной, а не протаскивать логгер, не ставить точку останова и не писать
Давайте сделаем простой
• само выражение;
• его значение;
• файл, строку и функцию, где это произошло.
В релизе же макрос превратится в пустышку и не повлияет на производительность.
Подключаем заголовки:
Сделаем вспомогательную функцию
Теперь объявим макрос
Проверим на простом примере:
При запуске debug-сборки увидим что-то вроде:
А в релизной сборке (
🔥 В итоге у тебя есть удобный
📣 C++ Ready | #практика
Иногда хочется быстро подсмотреть значение переменной, а не протаскивать логгер, не ставить точку останова и не писать
std::cout << ... руками. Давайте сделаем простой
DBG(expr), который в debug-сборке печатает:• само выражение;
• его значение;
• файл, строку и функцию, где это произошло.
В релизе же макрос превратится в пустышку и не повлияет на производительность.
Подключаем заголовки:
#include <iostream>
#include <string_view>
#include <source_location>
Сделаем вспомогательную функцию
debug_print, которая принимает значение, текст выражения и информацию о месте вызова:template <typename T>
void debug_print(const T& value,
std::string_view expr,
const std::source_location& loc = std::source_location::current())
{
std::cerr << "[DBG] " << expr << " = " << value << "\n"
<< " at " << loc.file_name() << ":" << loc.line()
<< " in " << loc.function_name() << "\n";
}
Теперь объявим макрос
DBG, который работает только в debug-сборке (пока не определён NDEBUG):#ifndef NDEBUG
#define DBG(expr) debug_print((expr), #expr)
#else
#define DBG(expr) ((void)0)
#endif
Проверим на простом примере:
#include <vector>
int main() {
int x = 42;
std::vector<int> v{1, 2, 3};
DBG(x);
DBG(v.size());
DBG(v[1] + x);
return 0;
}
При запуске debug-сборки увидим что-то вроде:
[DBG] x = 42
at example.cpp:8 in main
[DBG] v.size() = 3
at example.cpp:9 in main
[DBG] v[1] + x = 44
at example.cpp:10 in main
А в релизной сборке (
-DNDEBUG) все вызовы DBG(...) тихо исчезнут на этапе компиляции.DBG-макрос на чистом стандарте C++20: в отладке он показывает выражения с контекстом (файл, строка, функция), а в релизе не оставляет ни следа в бинарнике.Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥13❤8
Антипаттерн: # define DEBUG 1 vs NDEBUG
Часто для отладочного кода делают что-то вроде
Работает? Да.
Надёжно? Не всегда.
Проблема в том, что такой
В стандарте C++ уже есть специальный макрос для этого —
Именно на него завязаны
Плохой вариант:
Если забыть убрать
Правильный вариант — ориентироваться на
Теперь всё согласовано:
• debug-сборка → логирование и проверки есть
• release (
Точно так же стоит писать и свои отладочные макросы:
🔥 Использование
📣 C++ Ready | #практика
Часто для отладочного кода делают что-то вроде
#define DEBUG 1 и оборачивают всё в #ifdef DEBUG.Работает? Да.
Надёжно? Не всегда.
Проблема в том, что такой
DEBUG — не стандартный, и он легко расходится с реальным типом сборки. В итоге можно случайно оставить отладочный код в релизе.В стандарте C++ уже есть специальный макрос для этого —
NDEBUG. Если NDEBUG не определён, значит это debug-сборка. Если NDEBUG определён, значит релиз.Именно на него завязаны
assert и многие стандартные проверки.Плохой вариант:
#define DEBUG 1
void foo(int x) {
#ifdef DEBUG
std::cout << "x = " << x << "\n";
#endif
work(x);
}
Если забыть убрать
DEBUG или определить его не там — отладочный код спокойно уедет в прод.Правильный вариант — ориентироваться на
NDEBUG:void foo(int x) {
#ifndef NDEBUG
std::cerr << "x = " << x << "\n";
#endif
work(x);
}Теперь всё согласовано:
• debug-сборка → логирование и проверки есть
• release (
-DNDEBUG) → код полностью вырезается компиляторомТочно так же стоит писать и свои отладочные макросы:
#ifndef NDEBUG
#define DBG_LOG(msg) std::cerr << msg << "\n"
#else
#define DBG_LOG(msg) ((void)0)
#endif
NDEBUG — это не мелочь, а дисциплина сборки: отладочный код никогда не попадёт в релиз случайно, и всё поведение будет согласовано со стандартными механизмами C++.Please open Telegram to view this post
VIEW IN TELEGRAM
❤22👍13🔥7
Как min/max из windows.h ломают std::min/std::max?
На Windows легко словить странную ошибку компиляции в невинном месте:
Причина:
Правильный вариант — отключить эти макросы до подключения Windows-заголовков:
Если
И есть аварийный трюк, если нельзя менять include-порядок: обернуть имя в скобки — тогда макрос не сработает:
🔥 Итог: на Windows держи правило — `NOMINMAX` включён всегда, иначе стандартная библиотека будет ломаться в самых неожиданных местах.
📣 C++ Ready | #практика
На Windows легко словить странную ошибку компиляции в невинном месте:
#include <windows.h>
#include <algorithm>
auto m = std::min(a, b);
Причина:
windows.h (и некоторые связанные хедеры) могут определить макросы min/max. Тогда std::min(a, b) превращается в макро-подстановку, и начинает “ехать” всё, что выглядит как вызов функции.Правильный вариант — отключить эти макросы до подключения Windows-заголовков:
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <algorithm>
auto m = std::min(a, b);
Если
windows.h уже подключили (или его подтянула зависимость), можно убрать макросы точечно:#include <windows.h>
#undef min
#undef max
#include <algorithm>
И есть аварийный трюк, если нельзя менять include-порядок: обернуть имя в скобки — тогда макрос не сработает:
auto m = (std::min)(a, b);
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22❤9🤝9