Pull to refresh

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
КДПВ — 7 (семь) мегабайт!
Зачем?
Чтобы ещё раз показать, что макросы — зло.
Ну куда же без макросов:
#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.
Этот момент тоже описан в статье в следующем после прокомментированного вами абзаце.
А {...} не делают то же самое?
Если оставить только скобки, то в коде будет ошибка компиляции
if (true)
  MACRO(foo); // <- "Лишняя" `;`
else // <- Ошибка: оторванный `else`
  /* что-то еще */;
Лучше — совсем избегать макросы. Из-за огромного числа способов внести побочные эффекты, а также из-за того, что макросы не могут иметь никакую другую область видимости кроме глобальной.

Ну в C это невозможно, а вот современный C++ предоставляет богатые возможности, чтобы единственным сценарием использования макросов была условная компиляция.

Как это невозможно. Описать функцию вместо макроса? Другое дело, что вас могут пугать незначительные издержки на вызов функции, что в 99% не является критическим местом.

Не, ну учитывая, что макросы в конечном итоге развёртываются в код, логично, что без макросов обойтись можно. Просто в случае C это будет жуткое дублирование кода, а это сама по себе плохая практика.

Что-то я в упор не вижу кейс, где функция вместо макроса приведёт к дублированию кода. Было бы неплохо если бы вы привели пример.

Да хоть тот же макрос SWAP из статьи. В случае C вам придётся объявить по соответствующей функции для каждого из типов.

Так, хорошо, в C нет шаблонов, тут соглашусь. Но что заставляет разработчиков в 2021 году писать на C? Разве что для поддержки старого кода.
Спроси (или почитай ответ) Линуса или эмбедщиков.
Вся встройка, VoIP, Linux kernel и много чего ещё до сих пор на чистом С, без плюсов.
Простой и надежный язык, если использовать его правильно и регулярно проверяться valgrind и статикой. Намного понятнее С++, быстрее, меньше требует. На TIOBE вышел на первое место, сместив Java на второе.
  • X Macro и все его плюшки.
  • Получение номера строки и имени текущего файла исходников для вызываемой функции (например лога)

Еще макросы, в отличие от лямбд и инлайн-функций, позволяют менять control-flow (т.е. делать return или break из внешней области).

В C++ интроспекция типов, например, пока не может быть сделана без макросов.
UFO landed and left these words here
Разумеется разные языки. Всё ещё считаю макросы bad practice, учитывая что есть безопасные аналоги: функции и те же перечисления для констант.

Вот один из моих любимых примеров использования макросов. Программа, кстати, очень серьезная: это победитель конкурса на программу на С не длиннее 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

UFO landed and left these words here
#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’
UFO landed and left these words here
Я и сейчас раскрываю приватные объявления при сборке юнит тестов, чтобы не заморачиваться с наследованием классов.
Меня удивило предложение использовать двоеточия в макросах
UFO landed and left these words here

Вроде бы это нарушение one definition rule, с теоретически побочным эффектом undefined behaviour

UFO landed and left these words here

В 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, если в такой единице трансляции включается заголовочный файл стандартной библиотеки.


На практике такого пока не наблюдается, но не хотелось бы получить проблемы на ровном месте, даже в теории.

UFO landed and left these words here
Каждый compilation unit обрабатывается (препроцессором и затем компилятором) независимо от остальных.
Соглашусь с популярным мнением что препроцессор это не часть языка, и если есть возможность избегать макросов то лучше это сделать.
«Колбаса» начинается когда макрос генерирует макросы которые разворачиваются в другие макроссы, будто это эффективный инструмент для кодогенерации.

Чисто субьективно: IntelliSense плачет, я плачу, люди которые читаю код после меня тоже страдают — зачем это все? Чтобы «красиво» написать все в 2 строки, а не в 4 понятно?
В современном С++ можно спокойно обходиться без макросов вовсе, используя только шаблоны и inline-функции.
Как в С++ без макросов реализовать конкатенацию ##?

Ну и __FILE__ __LINE__ незаменимы.
Но в С++20 (
std::source_location
Ну и __FILE__ __LINE__ незаменимы.
Не поделитесь примером? __LINE__ необходим в protothreads и удобен в реализации КА, но когда, кроме сборки и сообщения об ошибках, нужен __FILE__?
UFO landed and left these words here
Сталкивался, решал уникальными строками сообщений об ошибках в xcase. Зря я так, видимо :)
Cлияние строк на С++ во время компиляции: stackoverflow.com/questions/38955940/how-to-concatenate-static-strings-at-compile-time/62823211#62823211
Технически реализация стала возможна со стандарта С++11, в данном случае автор кода пользовался возможностями из С++17.
И как там по человечески будет аналог PRINT_VAL из статьи? (у меня получился сюр при попытке вызова)
#define PRINT_VAL(val) printf("Value of %s is %d", #val, val);
Это уже другой вопрос. Я отвечал про конкатенацию двух или более строк через оператор препроцессора ##, а не про получение самого исходного текста переданного параметра через оператор #.
В исходном вопросе различия не было. С++ решение здесь очень ущербно.

Молчу уже про D =)
потому что теперь после MACRO() нужно будет ставить точку с запятой

Это основная и единственная причина так делать
Как-то проскочили мимо такой полезной вещи, как стандартные предопределенные макросы:
__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 не отваливалась после умников оптимзаторов?

PS: у вас никогда std::min не отваливалась после умников оптимзаторов?

Чаще всего оно отваливается после, проблема стара как WinAPI
#include <windows.h>

гуглить применение макроса NOMINMAX для WinAPI если ни когда не слышали.

Если в кратце, в недрах WinAPI, кажется в windef.h определены макросы min/max:
#ifndef NOMINMAX
#define min(x,y) ((x) < (y) ? (x) : (y))
#define max(x,y) ((x) > (y) ? (x) : (y))
#endif
В современном С++ можно спокойно обходиться без макросов вовсе

А вы знаете что #pragma once в стандарт не включен? И что иногда без него никак (поэтому все библиотеки используют дурацкий но незаменимый include guard

думаю речь шла про самый последний стандарт, где вместо инклудов можно использовать модули, но это не точно.

(1) Ожидал увидеть — а сейчас тоже самое, но на безопасных конструкциях C++ constexp, inline, template,…
(2) Пост — реклама моего блога вне хабр платформы?

Only those users with full accounts are able to leave comments. Log in, please.