Comments 56
Кажется, в разделе "Еще немного примеров" пропущен кусок кода.
Банальный пример — объявим несколько функций сложения для работы с разными типами данных:
Расскажите лучше о примерах которые нельзя реализовать через шаблоны, что куда более надежно чем макросы.
#define SWAP(type, a, b) type tmp = a; a = b; b = tmp;
тут вы оставили переменную `tmp` в общей области видимости, да и кроме того добавлять тип в этот макрос выглядит не очень красивым решением, уж лучше что-то вроде
#define SWAP(a, b) do{ decltype(b) tmp = a; a = b; b = tmp;}while(0)
Но и это ерунда, потому что в таком случае проще использовать шаблонную функцию, а еще проще взять std::swap
Зачем?
#define container_of(ptr, type, member) ({\
const typeof(((type *)0)->member) * __mptr = (ptr);\
(type *)((char *)__mptr - offsetof(type, member)); })
Но вообще внешние шаблонизаторы и скрипты для генерации кода могут сделать куда больше и иногда значительно удобней. Например загрузку opengl разных ревизий.
ps: Что бы визуально отличать макросы их рекомендуют называть большими буквами.
Именно для того, чтобы избежать подобных багов, у макросов должна быть объявлена своя область видимости. Для удобства в этих целях принято использовать цикл do {} while (0);.
Цель конструкции do {} while (0); немного другая. Не объявить область видимости, это и обычными скобками можно сделать
Цикл do/while уникален тем, что является единственной (почти) в языке С грамматической конструкцией, которая формирует блок, и при этом всегда безусловно заканчивается на ;. Благодаря этой замыкающей; мы можем более естественным образом использовать do {… } while (0) внутри составных макросов для объединения их в единый statement.
Ну в C это невозможно, а вот современный C++ предоставляет богатые возможности, чтобы единственным сценарием использования макросов была условная компиляция.
Не, ну учитывая, что макросы в конечном итоге развёртываются в код, логично, что без макросов обойтись можно. Просто в случае C это будет жуткое дублирование кода, а это сама по себе плохая практика.
Еще макросы, в отличие от лямбд и инлайн-функций, позволяют менять control-flow (т.е. делать return или break из внешней области).
Вот один из моих любимых примеров использования макросов. Программа, кстати, очень серьезная: это победитель конкурса на программу на С не длиннее 512 символов без пробелов, которая генерит максимальное число (значность int бесконечная)
loader.c
define R { return
define P P (
define L L (
define T S (v, y, c,
define C ),
define X x)
define F );}
int r, a;
P y, X
R y — ~y << x;
}
Z (X
R r = x % 2? 0: 1 + Z (x / 2 F
L X
R x / 2 >> Z (x F
define U = S(4,13,-4,
T t)
{
int
f = L t C
x = r;
R
f — 2?
f > 2?
f — v? t — (f > v) * c: y:
P f, P T L X C
S (v+2, t U y C c, Z (X )))
:
A (T L X C
T Z (X ) F
}
A (y, X
R L y) — 1
? 5 << P y, X
: S (4, x, 4, Z ® F
define B (x /= 2) % 2 && (
D (X
{
int
f,
d,
c = 0,
t = 7,
u = 14;
while (x && D (x — 1 C B 1))
d = L L D (X ) C
f = L r C
x = L r C
c — r || (
L u) || L r) — f ||
B u = S (4, d, 4, r C
t = A (t, d) C
f / 2 & B c = P d, c C
t U t C
u U u) )
C
c && B
t = P
~u & 2 | B
u = 1 << P L c C u) C
P L c C t) C
c = r C
u / 2 & B
c = P t, c C
u U t C
t = 9 );
R a = P P t, P u, P x, c)) C
a F
}
main ()
R D (D (D (D (D (99)))) F
#define private: public:А разве так прокатит???
А вы думаете — почему в языке есть
#pragma once
или #ifndef __CODE_CPP
#define __CODE_CPP
#endif
?Я про двоеточие
warning: ISO C++11 requires whitespace after the macro name
error: expected primary-expression before ‘public’
note: in expansion of macro ‘private’
error: ISO C++ forbids declaration of ‘type name’ with no type [-fpermissive]
note: in expansion of macro ‘private’
error: expected ‘;’ at end of member declaration
note: in expansion of macro ‘private’
Вроде бы это нарушение one definition rule, с теоретически побочным эффектом undefined behaviour
В hren.hpp определено
class Hren
{
private:
int a;
};
В hren.cpp лежит реализация, всё ок
#include "hren.hpp"
...
В hack_hren.cpp хотим по какой-то причине иметь доступ ко внутренносятм
#define private public
#include "hren.hpp"
...
Разные определения Hren в разных единицах трансляции -> ODR.
Еще в https://stackoverflow.com/a/27779038/1355844 утверждается, что само по себе переопределение ключевых слов — это UB, если в такой единице трансляции включается заголовочный файл стандартной библиотеки.
На практике такого пока не наблюдается, но не хотелось бы получить проблемы на ровном месте, даже в теории.
«Колбаса» начинается когда макрос генерирует макросы которые разворачиваются в другие макроссы, будто это эффективный инструмент для кодогенерации.
Чисто субьективно: IntelliSense плачет, я плачу, люди которые читаю код после меня тоже страдают — зачем это все? Чтобы «красиво» написать все в 2 строки, а не в 4 понятно?
В современном С++ можно спокойно обходиться без макросов вовсе, используя только шаблоны и inline-функции.Как в С++ без макросов реализовать конкатенацию ##?
Ну и __FILE__ __LINE__ незаменимы.
Ну и __FILE__ __LINE__ незаменимы.Не поделитесь примером? __LINE__ необходим в protothreads и удобен в реализации КА, но когда, кроме сборки и сообщения об ошибках, нужен __FILE__?
Технически реализация стала возможна со стандарта С++11, в данном случае автор кода пользовался возможностями из С++17.
потому что теперь после MACRO() нужно будет ставить точку с запятой
Это основная и единственная причина так делать
#define true false
Удачной отладки :)
__cplusplus — определяется как целочисленное литеральное значение, если компилируется как C++.
__DATE__ — дата компиляции текущего файла исходного кода.
__FILE__ — имя текущего файла исходного кода.
__LINE__ определяется как целочисленный номер строки в текущем файле исходного кода.
__STDC_NO_THREADS__ — определяется как 1, если реализация не поддерживает необязательные стандартные потоки.
__STDC_VERSION__ — определяется при компиляции в виде C, а также одного из вариантов /std C11 или C17. Он разворачивается в 201112L для /std:c11 и в 201710L для /std:c17.
__STDCPP_THREADS__ определяется как 1, только если программа может иметь только один поток выполнения и cкомпилирована как C++.
__TIME__ — время, в течение которого выполняется преобразование предварительно обработанной единицы трансляции.
В Visual Studio есть еще свои макросы, но это тема на целую статью.
Правило 0: при любой возможности отдавать предпочтение другим языковым средствам.
PS: у вас никогда std::min не отваливалась после умников оптимзаторов?
В современном С++ можно спокойно обходиться без макросов вовсе
А вы знаете что #pragma once в стандарт не включен? И что иногда без него никак (поэтому все библиотеки используют дурацкий но незаменимый include guard
(1) Ожидал увидеть — а сейчас тоже самое, но на безопасных конструкциях C++ constexp, inline, template,…
(2) Пост — реклама моего блога вне хабр платформы?
Макросы в С и С++