Когда полезны макросы C++?

Препроцессор C заслуженно опасается и избегается сообществом C++. Встроенные функции, константы и шаблоны обычно являются более безопасной и превосходной альтернативой #define.

Следующий макрос:

#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)  

никоим образом не превосходит безопасный тип:

inline bool succeeded(int hr) { return hr >= 0; }

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

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

Статическое утверждение
Özgür 08.03.2009 03:42

Однажды я взял приложение C++, полное макросов, на сборку которого ушло 45 минут, заменил макросы встроенными функциями и сократил сборку до менее 15 минут.

endian 23.10.2008 11:37

Тема посвящена контекстам, в которых макросы полезны, а не контекстам, в которых они неоптимальны.

underscore_d 26.05.2017 00:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
178
3
93 680
38
Перейти к ответу Данный вопрос помечен как решенный

Ответы 38

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

#define ASSERT_RETURN(condition, ret_val) \
if (!(condition)) { \
    assert(false && #condition); \
    return ret_val; }

// should really be in a do { } while(false) but that's another discussion.

Выброс исключения кажется мне лучшей альтернативой.

einpoklum 20.04.2016 14:11

При написании расширений Python C (++) исключения распространяются путем установки строки исключения с последующим возвратом -1 или NULL. Таким образом, макрос может значительно сократить шаблонный код.

black_puppydog 14.10.2016 15:59

Часто я получаю такой код:

int SomeAPICallbackMethod(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }
int AnotherCallback(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }
int YetAnotherCallback(long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx) { ... }

В некоторых случаях я использую следующее, чтобы облегчить себе жизнь:

#define APIARGS long a, long b, SomeCrazyClass c, long d, string e, string f, long double yx
int SomeAPICallbackMethod(APIARGS) { ... } 

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

В этой ситуации вам может быть лучше создать структуру CallbackArg.

Josh Matthews 19.09.2008 08:24

Вы не можете выполнить короткое замыкание аргументов вызова функции, используя обычный вызов функции. Например:

#define andm(a, b) (a) && (b)

bool andf(bool a, bool b) { return a && b; }

andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
andf(x, y) // y will always be evaluated

Может быть, более общий момент: функции оценивают свои аргументы только один раз. Макросы могут оценивать аргументы большее или меньшее количество раз.

Steve Jessop 19.09.2008 00:06

@ [Greg Rogers] все, что делает препроцессор макроса, - это заменяет текст. Как только вы это поймете, в этом больше не должно быть никаких тайн.

1800 INFORMATION 19.09.2008 00:08

Вы можете получить эквивалентное поведение, создав шаблон и f вместо того, чтобы заставлять оценку bool перед вызовом функции. Я бы не понял, что вы сказали правду, если бы не попробовал это на себе. Интересно.

Greg Rogers 19.09.2008 00:11

Как именно вы могли сделать это с помощью шаблона?

1800 INFORMATION 19.09.2008 01:47

Скрытие операций короткого замыкания за макросом стиля функции - одна из вещей, которую я действительно не хочу видеть в производственном коде.

MikeMB 03.07.2016 03:08

Одно из распространенных применений - определение среды компиляции. Для кросс-платформенной разработки вы можете написать один набор кода, скажем, для Linux, а другой - для Windows, когда для ваших целей еще не существует кроссплатформенной библиотеки.

Итак, в грубом примере кроссплатформенный мьютекс может иметь

void lock()
{
    #ifdef WIN32
    EnterCriticalSection(...)
    #endif
    #ifdef POSIX
    pthread_mutex_lock(...)
    #endif
}

Для функций они полезны, когда вы хотите явно игнорировать безопасность типов. Например, во многих приведенных выше и ниже примерах выполнения ASSERT. Конечно, как и многие другие функции C / C++, вы можете выстрелить себе в ногу, но язык дает вам инструменты и позволяет вам решать, что делать.

Поскольку спрашивающий спросил: это можно сделать без макросов, включив разные заголовки через разные пути включения для каждой платформы. Хотя я склонен согласиться с тем, что макросы зачастую удобнее.

Steve Jessop 18.09.2008 23:59

Я поддерживаю это. Если вы начнете использовать для этой цели макросы, код может быстро стать менее читаемым.

Nemanja Trifunovic 19.09.2008 00:38

Очевидное включает охранников

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

Если вы хотите сделать строку из выражения, лучшим примером для этого является assert (#x превращает значение x в строку).

#define ASSERT_THROW(condition) \
if (!(condition)) \
     throw std::exception(#condition " is false");

Просто придирка, но я бы не стал ставить точку с запятой.

Michael Myers 19.09.2008 01:05

Я согласен, на самом деле я бы поставил его в do {} while (false) (чтобы предотвратить взлом), но я хотел, чтобы это было просто.

Motti 05.10.2008 22:24

Методы всегда должны быть полным, компилируемым кодом; макросы могут быть фрагментами кода. Таким образом, вы можете определить макрос foreach:

#define foreach(list, index) for(index = 0; index < list.size(); index++)

И используйте это так:

foreach(cookies, i)
    printf("Cookie: %s", cookies[i]);

Начиная с C++ 11, это заменено цикл for на основе диапазона.

Красиво, но осторожно, foreach BOOST определяет аргументы наоборот

Martin Beckett 03.10.2008 06:37

Я должен с вами не согласиться - это может быть полезно для вас, но снизит читаемость вашего кода и приведет к проблемам, если другой человек попытается определить тот же макрос. В этом случае лучше просто придерживаться стандарта for ().

Nik Reiman 06.10.2008 00:00

Кроме того, вам, вероятно, следует использовать итератор вместо индекса, поскольку std :: list :: size () должен будет каждый раз проходить весь список, поэтому было бы довольно неэффективно выполнять это сравнение в каждом цикле.

jonner 08.10.2008 04:29

+1 Если вы используете какой-то смехотворно сложный синтаксис итератора, написание макроса в стиле foreach может значительно упростить чтение и сопровождение вашего кода. Я сделал это, работает.

postfuturist 26.10.2008 01:01

Большинство комментариев совершенно неуместны до того, что макросы могут быть фрагментами кода, а не полным кодом. Но спасибо за придирки.

jdmichal 12.11.2008 23:15

@jonner: предполагается, что компилятор не оптимизирует вызов метода для каждой итерации. Я предполагаю, что это сделает любой современный, не наивный компилятор.

jdmichal 17.12.2008 22:07

@sqook: Как намекнул steveth45, я думаю, что идея цикла foreach сейчас достаточно повсеместна, поэтому должно быть очевидно, каковы ваши намерения.

jdmichal 17.12.2008 22:09

Это C, а не C++. Если вы работаете на C++, вам следует использовать итераторы и std :: for_each.

chrish 11.06.2009 23:19

Я не согласен, chrish. До лямбды for_each был неприятной вещью, потому что код, через который проходил каждый элемент, не был локальным для вызывающей точки. foreach (и я настоятельно рекомендую BOOST_FOREACH вместо ручного решения) позволяет держать код близко к месту итерации, что делает его более читабельным. Тем не менее, как только лямбда будет запущена, for_each снова может стать подходящим вариантом.

GManNickG 19.08.2009 03:50

И стоит отметить, что BOOST_FOREACH сам по себе макрос (но очень хорошо продуманный)

Tyler McHenry 19.08.2009 03:56

А если вы используете C++ 11, вы можете просто использовать новую конструкцию foreach.

Jed Schaaf 06.11.2013 23:02

@chrish Надеюсь, что нет. Кто-то действительно напортачил, если реализовал свой интерфейс как указатели на функции в своей структуре list. В C++ есть функции-члены. C не делает. Тогда каждый объект списка будет нести несколько сотен байтов указателей на функции. Полная трата ценного (стекового) пространства.

yyny 15.10.2016 00:41

Компиляторы могут отклонить ваш запрос на встраивание.

Макросы всегда будут на своем месте.

Что-то, что я считаю полезным, - это #define DEBUG для трассировки отладки - вы можете оставить его 1 на время отладки проблемы (или даже оставить его включенным в течение всего цикла разработки), а затем выключить, когда придет время для отправки.

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

David Thornley 23.10.2008 23:22

@DavidThornley Или это может быть не лучший оптимизирующий компилятор, такой как GCC или CLANG / LLVM. Некоторые компиляторы просто хрень.

Miles Rout 03.06.2014 08:08

Когда во время компиляции вы принимаете решение о специфическом поведении компилятора / ОС / оборудования.

Это позволяет вам сделать ваш интерфейс специфичным для Comppiler / OS / Hardware.

#if defined(MY_OS1) && defined(MY_HARDWARE1)
#define   MY_ACTION(a,b,c)      doSothing_OS1HW1(a,b,c);}
#elif define(MY_OS1) && defined(MY_HARDWARE2)
#define   MY_ACTION(a,b,c)      doSomthing_OS1HW2(a,b,c);}
#elif define(MY_SUPER_OS)
          /* On this hardware it is a null operation */
#define   MY_ACTION(a,b,c)
#else
#error  "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration"
#endif
Ответ принят как подходящий

В качестве оберток для функций отладки, чтобы автоматически передавать такие вещи, как __FILE__, __LINE__ и т. Д .:

#ifdef ( DEBUG )
#define M_DebugLog( msg )  std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif

Фактически, исходный фрагмент: << ФАЙЛ ":" << в порядке, ФАЙЛ генерирует строковую константу, которая будет объединена с ":" в одну строку препроцессором.

Frank Szczerba 20.03.2009 02:59

Для этого требуется только препроцессор, потому что __FILE__ и __LINE__также требуют препроцессора. Использование их в вашем коде похоже на вектор заражения препроцессора.

T.E.D. 30.04.2012 22:53

Внутренняя условная компиляция, чтобы преодолеть проблемы различий между компиляторами:

#ifdef ARE_WE_ON_WIN32
#define close(parm1)            _close (parm1)
#define rmdir(parm1)            _rmdir (parm1)
#define mkdir(parm1, parm2)     _mkdir (parm1)
#define access(parm1, parm2)    _access(parm1, parm2)
#define create(parm1, parm2)    _creat (parm1, parm2)
#define unlink(parm1)           _unlink(parm1)
#endif

В C++ то же самое можно получить с помощью встроенных функций: <code> #ifdef ARE_WE_ON_WIN32 <br> inline int close (int i) {return _close (i); } <br> #endif </code>

paercebal 23.10.2008 02:31

Это удаляет #define, но не #ifdef и #endif. Во всяком случае, я с тобой согласен.

Gorpik 23.10.2008 12:16

НИКОГДА не определяйте макросы нижнего регистра. Макросы для изменения функций - мой кошмар (спасибо Microsoft). Лучший пример - в первой строке. Многие библиотеки имеют функции или методы close. Затем, когда вы включаете заголовок этой библиотеки и заголовок с этим макросом, у вас возникает большая проблема, вы не можете использовать API библиотеки.

Marek R 28.05.2015 17:56

AndrewStein, видите ли вы какую-либо пользу от использования макросов в этом контексте по сравнению с предложением @paercebal? В противном случае кажется, что макросы на самом деле бесплатны.

einpoklum 20.04.2016 14:10

@einpoklum - я давно это не писал. Предложение @paercebal удаляет #defines. В то время нам это было нужно как для C, так и для C++, так что ...

Andrew Stein 20.04.2016 19:42

#ifdef WE_ARE_ON_WIN32 плз :)

Lightness Races in Orbit 03.08.2018 17:27

Для защиты заголовочного файла требуются макросы.

Есть ли другие области, в которых используются макросы требовать? Немного (если есть).

Есть ли еще какие-то ситуации, в которых макросы могут быть полезны? ДА!!!

Одно место, где я использую макросы, - это повторяющийся код. Например, при упаковке кода C++ для использования с другими интерфейсами (.NET, COM, Python и т. д.) Мне нужно перехватывать различные типы исключений. Вот как я это делаю:

#define HANDLE_EXCEPTIONS \
catch (::mylib::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e); \
} \
catch (::std::exception& e) { \
    throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \
} \
catch (...) { \
    throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \
}

Я должен поместить эти уловки в каждую функцию-оболочку. Вместо того, чтобы каждый раз вводить полные блоки catch, я просто набираю:

void Foo()
{
    try {
        ::mylib::Foo()
    }
    HANDLE_EXCEPTIONS
}

Это также упрощает обслуживание. Если мне когда-нибудь понадобится добавить новый тип исключения, мне нужно будет добавить только одно место.

Есть и другие полезные примеры: многие из них включают макросы препроцессора __FILE__ и __LINE__.

В любом случае, макросы очень полезны при правильном использовании. Макросы не злые - их злоупотребление злые.

В наши дни большинство компиляторов поддерживают #pragma once, поэтому я сомневаюсь, что защита действительно необходима.

1800 INFORMATION 19.09.2008 00:10

Они есть, если вы пишете для всех компиляторов, а не только для большинства ;-)

Steve Jessop 19.09.2008 00:13

Итак, вместо переносимых стандартных функций препроцессора вы рекомендуете использовать расширение препроцессора, чтобы избежать использования препроцессора? Мне это кажется смешным.

Logan Capaldo 08.03.2009 03:49

#pragma once ломается во многих распространенных системах сборки.

Miles Rout 03.06.2014 07:58

Для этого есть решение, не требующее макросов: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. А со стороны функций: void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }

MikeMB 03.07.2016 02:59

Что-то вроде

void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);

Так что вы можете, например,

assert(n == true);

и получите имя исходного файла и номер строки проблемы, распечатанные в вашем журнале, если n ложно.

Если вы используете обычный вызов функции, например

void assert(bool val);

вместо макроса все, что вы можете получить, это номер строки вашей функции assert, напечатанный в журнале, что было бы менее полезно.

Зачем изобретать велосипед, если реализации стандартной библиотеки уже предоставляют через <cassert> макрос assert(), который выгружает информацию о файле / строке / функции? (во всяком случае, во всех реализациях, которые я видел)

underscore_d 26.05.2017 00:39

Фреймворки модульного тестирования для C++, такие как UnitTest ++, в значительной степени вращаются вокруг макросов препроцессора. Несколько строк кода модульного теста превращаются в иерархию классов, которые было бы совсем не интересно печатать вручную. Я не знаю, как бы вы могли эффективно писать модульные тесты для C++ без чего-то вроде UnitTest ++ и его магии препроцессора.

Модульные тесты вполне можно писать без фреймворка. В конце концов, это действительно зависит только от того, какой результат вы хотите. Если вам все равно, подойдет простое значение выхода, указывающее на успех или неудачу.

Clearer 19.06.2018 16:44

Мы используем макросы __FILE__ и __LINE__ для диагностических целей при генерировании, перехвате и регистрации исключений с богатой информацией, вместе с автоматическими сканерами файлов журнала в нашей инфраструктуре контроля качества.

Например, бросающий макрос OUR_OWN_THROW может использоваться с типом исключения и параметрами конструктора для этого исключения, включая текстовое описание. Так:

OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));

Этот макрос, конечно, вызовет исключение InvalidOperationException с описанием в качестве параметра конструктора, но он также запишет сообщение в файл журнала, состоящее из имени файла и номера строки, в которой произошел выброс, и его текстового описания. Выброшенное исключение получит идентификатор, который также будет зарегистрирован. Если исключение когда-либо обнаруживается где-то еще в коде, оно будет помечено как таковое, а затем в файле журнала будет указано, что это конкретное исключение было обработано и, следовательно, не является причиной какого-либо сбоя, который может быть зарегистрирован позже. Необработанные исключения могут быть легко обнаружены нашей автоматизированной инфраструктурой контроля качества.

Я думаю, что этот трюк - умное использование препроцессора, которое нельзя эмулировать с помощью функции:

#define COMMENT COMMENT_SLASH(/)
#define COMMENT_SLASH(s) /##s

#if defined _DEBUG
#define DEBUG_ONLY
#else
#define DEBUG_ONLY COMMENT
#endif

Тогда вы можете использовать это так:

cout <<"Hello, World!" <<endl;
DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;

Вы также можете определить макрос RELEASE_ONLY.

Этот трюк не работает по стандарту. Он пытается создать маркер комментария через препроцессор, но комментарии должны быть удалены перед запуском препроцессора. Соответствующий компилятор вызовет здесь синтаксическую ошибку.

David Thornley 23.10.2008 23:20

Извините, Дэвид, но компилятор должен содержать вторую копию удаления комментария.

Joshua 14.08.2009 00:16

гораздо проще сделать флаг отладки глобальной const bool и использовать такой код: if (debug) cout << "..."; - макросы не нужны!

Stefan Monov 17.01.2010 19:51

@Stefan: В самом деле, это то, чем я сейчас занимаюсь. Любой достойный компилятор не будет генерировать код, если в этом случае отладка будет ложной.

Mathieu Pagé 18.01.2010 05:14

Допустим, мы проигнорируем такие очевидные вещи, как защиту заголовка.

Иногда вам нужно сгенерировать код, который нужно скопировать / вставить прекомпилятор:

#define RAISE_ERROR_STL(p_strMessage)                                          \
do                                                                             \
{                                                                              \
   try                                                                         \
   {                                                                           \
      std::tstringstream strBuffer ;                                           \
      strBuffer << p_strMessage ;                                              \
      strMessage = strBuffer.str() ;                                           \
      raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
   }                                                                           \
   catch(...){}                                                                \
   {                                                                           \
   }                                                                           \
}                                                                              \
while(false)

что позволяет вам кодировать это:

RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;

И может генерировать такие сообщения, как:

Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"

Обратите внимание, что смешивание шаблонов с макросами может привести к еще лучшим результатам (т. Е. Автоматическое создание значений рядом с именами переменных).

В других случаях вам понадобятся __FILE__ и / или __LINE__ некоторого кода, например, для генерации отладочной информации. Следующее является классическим для Visual C++:

#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "

Как и в следующем коде:

#pragma message(WRNG "Hello World")

он генерирует такие сообщения, как:

C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World

В других случаях вам нужно сгенерировать код с помощью операторов конкатенации # и ##, например, сгенерировать геттеры и сеттеры для свойства (это для довольно ограниченных случаев, сквозное).

В других случаях вы сгенерируете код, который не будет компилироваться при использовании через функцию, например:

#define MY_TRY      try{
#define MY_CATCH    } catch(...) {
#define MY_END_TRY  }

Что можно использовать как

MY_TRY
   doSomethingDangerous() ;
MY_CATCH
   tryToRecoverEvenWithoutMeaningfullInfo() ;
   damnThoseMacros() ;
MY_END_TRY

(тем не менее, я видел только такой код, который правильно использовал однажды)

И последнее, но не менее важное: знаменитый boost::foreach !!!

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

(Примечание: код скопирован / вставлен с главной страницы повышения)

Что (IMHO) лучше, чем std::for_each.

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

Используйте CPP только для того, что компилятор сделать не может. Например, RAISE_ERROR_STL должен использовать CPP только для определения сигнатуры файла, строки и функции и передать их функции (возможно, встроенной), которая сделает все остальное.

Rainer Blome 04.02.2016 17:20

Обновите свой ответ, чтобы он отражал C++ 11 и обратился к комментарию @ RainerBlome.

einpoklum 20.04.2016 14:13

@RainerBlome: Мы согласны. Макрос RAISE_ERROR_STL создан до C++ 11, поэтому в этом контексте он полностью оправдан. Насколько я понимаю (но у меня никогда не было возможности иметь дело с этими конкретными функциями), вы можете использовать вариативные шаблоны (или макросы?) В Modern C++, чтобы решить проблему более элегантно.

paercebal 24.04.2016 15:10

@einpoklum: «Пожалуйста, обновите свой ответ, чтобы он отражал C++ 11 и обратился к комментарию RainerBlome» № :-). . . Я считаю, что в лучшем случае я добавлю раздел для Modern C++ с альтернативными реализациями, уменьшающими или устраняющими необходимость в макросах, но суть в том, что макросы уродливы и злы, но когда вам нужно сделать что-то, чего компилятор не понимает , вы делаете это с помощью макросов.

paercebal 24.04.2016 15:14

Даже с C++ 11 многое из того, что делает ваш макрос, можно оставить функции: #include <sstream> #include <iostream> using namespace std; void trace(char const * file, int line, ostream & o) { cerr<<file<<":"<<line<<": "<< static_cast<ostringstream & >(o).str().c_str()<<endl; } struct Oss { ostringstream s; ostringstream & lval() { return s; } }; #define TRACE(ostreamstuff) trace(__FILE__, __LINE__, Oss().lval()<<ostreamstuff) int main() { TRACE("Hello " << 123); return 0; } Таким образом, макрос будет короче.

Rainer Blome 29.04.2016 16:28

Вам нужны макросы для идентификаторов ресурсов в Visual Studio, поскольку компилятор ресурсов понимает только их (т.е. он не работает с const или enum).

Иногда я использую макросы, чтобы определять информацию в одном месте, но по-разному использую их в разных частях кода. Это немного злое :)

Например, в "field_list.h":

/*
 * List of fields, names and values.
 */
FIELD(EXAMPLE1, "first example", 10)
FIELD(EXAMPLE2, "second example", 96)
FIELD(ANOTHER, "more stuff", 32)
...
#undef FIELD

Затем для общедоступного перечисления можно определить просто использование имени:

#define FIELD(name, desc, value) FIELD_ ## name,

typedef field_ {

#include "field_list.h"

    FIELD_MAX

} field_en;

А в частной функции инициализации все поля можно использовать для заполнения таблицы данными:

#define FIELD(name, desc, value) \
    table[FIELD_ ## name].desc = desc; \
    table[FIELD_ ## name].value = value;

#include "field_list.h"

Примечание: подобная техника может быть реализована даже без отдельного включения. См .: stackoverflow.com/questions/147267/…stackoverflow.com/questions/126277/…

Suma 24.10.2008 01:54

Вы можете использовать #defines для помощи в сценариях отладки и модульного тестирования. Например, создайте специальные варианты журналирования функций памяти и создайте специальный memlog_preinclude.h:

#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free

Скомпилируйте код, используя:

gcc -Imemlog_preinclude.h ...

Ссылка в вашем memlog.o на окончательное изображение. Теперь вы управляете malloc и т. д., Возможно, для целей ведения журнала или для имитации сбоев выделения для модульных тестов.

#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])

В отличие от «предпочтительного» шаблонного решения, обсуждаемого в текущем потоке, вы можете использовать его как постоянное выражение:

char src[23];
int dest[ARRAY_SIZE(src)];

Это можно сделать с помощью шаблонов более безопасным способом (который не будет компилироваться, если передан указатель, а не массив) stackoverflow.com/questions/720077/calculating-size-of-an-ar‌ ray /…

Motti 14.08.2009 00:17

Теперь, когда у нас есть constexpr в C++ 11, безопасную (не макро) версию также можно использовать в константном выражении. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }

David Stone 09.09.2015 05:02

Можете ли вы реализовать это как встроенную функцию?

#define my_free(x) do { free(x); x = NULL; } while (0)

template <class T> inline void destroy (T * & p) {delete p; р = 0; }

KTC 19.09.2008 08:48

Константы #define можно использовать в командной строке компилятора с помощью параметра -D или /D. Это часто бывает полезно при кросс-компиляции одного и того же программного обеспечения для нескольких платформ, потому что вы можете настроить файлы сборки для управления тем, какие константы определены для каждой платформы.

На последней работе я работал над антивирусным сканером. Чтобы упростить мне отладку, у меня повсюду зависло много журналов, но в таких приложениях с высоким спросом, как это, затраты на вызов функции просто слишком дороги. Итак, я придумал этот маленький макрос, который по-прежнему позволял мне включать ведение журнала отладки в версии выпуска на сайте клиентов, без затрат на вызов функции, проверял флаг отладки и просто возвращался без регистрации чего-либо или, если он был включен , будет вести журнал ... Макрос был определен следующим образом:

#define dbgmsg(_FORMAT, ...)  if ((debugmsg_flag  & 0x00000001) || (debugmsg_flag & 0x80000000))     { log_dbgmsg(_FORMAT, __VA_ARGS__);  }

Из-за VA_ARGS в функциях журнала это был хороший случай для такого макроса.

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

Макрос (ы) определяется как:

#define SECURITY_CHECK(lRequiredSecRoles) if (!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))

Затем мы могли бы просто разбросать проверки по всему пользовательскому интерфейсу, и он сообщит вам, каким ролям разрешено выполнять действие, которое вы пытались выполнить, если у вас еще не было этой роли. Причина для двух из них заключалась в том, чтобы вернуть значение в одних местах и ​​вернуться из функции void в других ...

SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR);

LRESULT CAddPerson1::OnWizardNext() 
{
   if (m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) {
      SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1;
   } else if (m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) {
      SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1;
   }
...

В любом случае, вот как я их использовал, и я не уверен, как это можно было сделать с помощью шаблонов ... Кроме этого, я стараюсь избегать их, если ДЕЙСТВИТЕЛЬНО не требуется.

Если у вас есть список полей, которые используются для множества вещей, например определение структуры, сериализацию этой структуры в / из некоторого двоичного формата, выполнение вставок в базу данных и т.д., тогда вы можете (рекурсивно!) использовать препроцессор, чтобы никогда не повторять свой список полей.

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

Конечно, та же самая общая идея широко используется в языках с правильным отражением - просто изучите класс и работайте с каждым полем по очереди. Выполнение этого в препроцессоре C непрочно, неразборчиво и не всегда переносимо. Поэтому я упоминаю об этом с некоторым трепетом. Тем не менее, вот оно ...

(Обновлено: теперь я вижу, что это похоже на то, что @Andrew Johnson сказал 9/18; однако идея рекурсивного включения того же файла продвигает идею немного дальше.)

// file foo.h, defines class Foo and various members on it without ever repeating the
// list of fields.

#if defined( FIELD_LIST )
   // here's the actual list of fields in the class.  If FIELD_LIST is defined, we're at
   // the 3rd level of inclusion and somebody wants to actually use the field list.  In order
   // to do so, they will have defined the macros STRING and INT before including us.
   STRING( fooString )
   INT( barInt )   
#else // defined( FIELD_LIST )

#if !defined(FOO_H)
#define FOO_H

#define DEFINE_STRUCT
// recursively include this same file to define class Foo
#include "foo.h"
#undef DEFINE_STRUCT

#define DEFINE_CLEAR
// recursively include this same file to define method Foo::clear
#include "foo.h"
#undef DEFINE_CLEAR

// etc ... many more interesting examples like serialization

#else // defined(FOO_H)
// from here on, we know that FOO_H was defined, in other words we're at the second level of
// recursive inclusion, and the file is being used to make some particular
// use of the field list, for example defining the class or a single method of it

#if defined( DEFINE_STRUCT )
#define STRING(a)  std::string a;
#define INT(a)     long a;
   class Foo
   {
      public:
#define FIELD_LIST
// recursively include the same file (for the third time!) to get fields
// This is going to translate into:
//    std::string fooString;
//    int barInt;
#include "foo.h"
#endif

      void clear();
   };
#undef STRING
#undef INT
#endif // defined(DEFINE_STRUCT)


#if defined( DEFINE_ZERO )
#define STRING(a) a = "";
#define INT(a) a = 0;
#define FIELD_LIST
   void Foo::clear()
   {
// recursively include the same file (for the third time!) to get fields.
// This is going to translate into:
//    fooString = "";
//    barInt=0;
#include "foo.h"
#undef STRING
#undef int
   }
#endif // defined( DEFINE_ZERO )

// etc...


#endif // end else clause for defined( FOO_H )

#endif // end else clause for defined( FIELD_LIST )

Бояться препроцессора C - все равно что бояться ламп накаливания только потому, что у нас есть люминесцентные лампы. Да, первое может быть {электричество | время программиста} неэффективно. Да, вы можете (буквально) обжечься ими. Но они могут выполнить свою работу, если вы правильно с ней справитесь.

Когда вы программируете встроенные системы, C используется как единственный вариант, кроме ассемблера. После программирования на настольном компьютере с помощью C++ и последующего переключения на более мелкие встроенные цели, вы научитесь перестать беспокоиться о «неэлегантности» стольких простых функций C (включая макросы) и просто пытаетесь выяснить, как лучше и безопаснее использовать их. Особенности.

Александр Степанов говорит:

When we program in C++ we should not be ashamed of its C heritage, but make full use of it. The only problems with C++, and even the only problems with C, arise when they themselves are not consistent with their own logic.

Я считаю, что это неправильное отношение. То, что вы можете научиться «правильно с этим справляться», не означает, что это стоит чьего-либо времени и усилий.

Neil G 08.04.2010 03:20

По большей части:

  1. Включить охранников
  2. Условная компиляция
  3. Отчетность (предопределенные макросы, такие как __LINE__ и __FILE__)
  4. (редко) Дублирование повторяющихся шаблонов кода.
  5. В коде вашего конкурента.

Ищете помощь о том, как реализовать номер 5. Можете ли вы помочь мне найти решение?

Max 14.02.2020 16:49

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

void Log::trace(const char *pszMsg) {
    if (!bDebugBuild) {
        return;
    }
    // Do the logging
}

...

log.trace("Inside MyFunction");

Вы можете иметь:

#ifdef _DEBUG
#define LOG_TRACE log.trace
#else
#define LOG_TRACE void
#endif

...

LOG_TRACE("Inside MyFunction");

Если _DEBUG не определен, это вообще не будет генерировать никакого кода. Ваша программа будет работать быстрее, и текст для журнала трассировки не будет компилироваться в ваш исполняемый файл.

Подобного эффекта можно добиться, играя с шаблонами.

Sergey Skoblikov 28.11.2008 20:11

встроенный void LogTrace (const char *) {if (DEBUG) doTrace (); } следует оптимизировать в выпускных сборках.

Motti 15.06.2009 22:35

Строковые константы иногда лучше определять как макросы, поскольку со строковыми литералами можно больше, чем с const char *.

например Строковые литералы могут быть легко соединяется.

#define BASE_HKEY "Software\Microsoft\Internet Explorer\"
// Now we can concat with other literals
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "Settings", &settings);
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "TypedURLs", &URLs);

Если бы использовался const char *, то для выполнения конкатенации во время выполнения нужно было бы использовать какой-то строковый класс:

const char* BaseHkey = "Software\Microsoft\Internet Explorer\";
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "Settings").c_str(), &settings);
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "TypedURLs").c_str(), &URLs);

В C++ 11 я считаю это самой важной частью (кроме включения охранников). Макросы - действительно лучшее, что у нас есть для обработки строк во время компиляции. Это функция, которую, я надеюсь, мы получим в C++ 11 ++.

David Stone 16.11.2012 07:34

Это ситуация, которая побудила меня пожелать макросов на C#.

Rawling 05.12.2012 19:25

Хотел бы я +42 к этому. Очень важный, хотя и не часто вспоминаемый аспект строковых литералов.

Daniel Kamil Kozar 25.03.2016 14:22

Некоторые очень продвинутые и полезные вещи могут быть созданы с использованием препроцессора (макросов), чего вы никогда не сможете сделать, используя «языковые конструкции» C++, включая шаблоны.

Примеры:

Создание чего-либо как идентификатора C, так и строки

Простой способ использовать переменные перечислимого типа как строку в C

Ускорение метапрограммирования препроцессора

Третья ссылка не работает фуи

Robin Hartland 29.06.2015 00:01

Взгляните на файлы stdio.h и sal.h в vc12, чтобы лучше понять.

Elshan 17.05.2016 13:54

Я использовал препроцессор для вычисления чисел с фиксированной точкой из значений с плавающей точкой, используемых во встроенных системах, которые не могут использовать плавающую точку в скомпилированном коде. Удобно иметь всю свою математику в единицах реального мира и не думать о них в фиксированной точке.

Пример:

// TICKS_PER_UNIT is defined in floating point to allow the conversions to compute during compile-time.
#define TICKS_PER_UNIT  1024.0


// NOTE: The TICKS_PER_x_MS will produce constants in the preprocessor.  The (long) cast will
//       guarantee there are no floating point values in the embedded code and will produce a warning
//       if the constant is larger than the data type being stored to.
//       Adding 0.5 sec to the calculation forces rounding instead of truncation.
#define TICKS_PER_1_MS( ms ) (long)( ( ( ms * TICKS_PER_UNIT ) / 1000 ) + 0.5 )

Это можно сделать с помощью встроенной функции

Motti 19.08.2009 11:01

Будут ли встроенные функции использовать другие встроенные функции и предотвращать попадание значений с плавающей запятой в окончательный код? Приведенный выше пример довольно прост, но я использовал этот метод для вычисления скорости вращения колеса через несколько шестерен с разными передаточными числами на основе числа оборотов двигателя. Макросы определяют каждый уровень преобразования.

dwj 19.08.2009 19:57

Еще один макрос foreach. T: тип, c: контейнер, i: итератор

#define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i)
#define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)

Использование (демонстрация концепции, а не реальная):

void MultiplyEveryElementInList(std::list<int>& ints, int mul)
{
    foreach(std::list<int>, ints, i)
        (*i) *= mul;
}

int GetSumOfList(const std::list<int>& ints)
{
    int ret = 0;
    foreach_const(std::list<int>, ints, i)
        ret += *i;
    return ret;
}

Доступны лучшие реализации: Google "BOOST_FOREACH"

Доступны хорошие статьи: Условная любовь: FOREACH Redux (Эрик Ниблер) http://www.artima.com/cppsource/foreach.html

Возможно, наибольшее использование макросов находится в независимой от платформы разработке. Подумайте о случаях несоответствия типов - с макросами вы можете просто использовать разные файлы заголовков, например: --WIN_TYPES.H

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--program.h

#ifdef WIN32
#define TYPES_H "WINTYPES.H"
#else 
#define TYPES_H "POSIX_TYPES.H"
#endif

#include TYPES_H

На мой взгляд, гораздо удобнее, чем реализовать его другими способами.

#define COLUMNS(A,B) [(B) - (A) + 1]

struct 
{
    char firstName COLUMNS(  1,  30);
    char lastName  COLUMNS( 31,  60);
    char address1  COLUMNS( 61,  90);
    char address2  COLUMNS( 91, 120);
    char city      COLUMNS(121, 150);
};

Полагаю, там не хватает char?

underscore_d 26.05.2017 00:47

Это почти сработало бы в SQL ;-), но не в C, поскольку имя типа должно стоять перед именем переменной. Я думаю, вы могли бы скрыть это с помощью макро-сигнатуры вроде #define COLUMNS(name, from, to) name char[ (from) - (to) + 1 ].

underscore_d 26.05.2017 02:11

Я использую макросы, чтобы легко определять исключения:

DEF_EXCEPTION(RessourceNotFound, "Ressource not found")

где DEF_EXCEPTION - это

#define DEF_EXCEPTION(A, B) class A : public exception\
  {\
  public:\
    virtual const char* what() const throw()\
    {\
      return B;\
    };\
  }\

Кажется, что VA_ARGS пока упоминается только косвенно:

При написании универсального кода C++ 03, когда вам нужно переменное количество (универсальных) параметров, вы можете использовать макрос вместо шаблона.

#define CALL_RETURN_WRAPPER(FnType, FName, ...)          \
  if ( FnType theFunction = get_op_from_name(FName) ) {   \
    return theFunction(__VA_ARGS__);                     \
  } else {                                               \
    throw invalid_function_name(FName);                  \
  }                                                      \
/**/

Note: In general, the name check/throw could also be incorporated into the hypothetical get_op_from_name function. This is just an example. There might be other generic code surrounding the VA_ARGS call.

Как только мы получим вариативные шаблоны с C++ 11, мы сможем «правильно» решить эту проблему с помощью шаблона.

Повторение кода.

Взгляните на библиотека препроцессора Boost, это своего рода мета-метапрограммирование. В теме-> мотивация вы можете найти хороший пример.

Я почти во всех, если не во всех случаях - повторения кода можно избежать с помощью вызовов функций.

einpoklum 20.04.2016 14:13

@einpoklum: Я не согласен. Взгляните на ссылку

Ruggero Turra 20.04.2016 16:19

Макросы полезны для имитации синтаксиса операторов switch:

switch(x) {
case val1: do_stuff(); break;
case val2: do_other_stuff();
case val3: yet_more_stuff();
default:   something_else();
}

для нецелочисленных типов значений. В этом вопросе:

Использование строк в операторах switch - где мы находимся в C++ 17?

вы найдете ответы, предлагающие некоторые подходы с использованием лямбда-выражений, но, к сожалению, больше всего нам подходят макросы:

SWITCH(x)
CASE val1  do_stuff(); break;
CASE val2  do_other_stuff();
CASE val3  yet_more_stuff();
DEFAULT    something_else();
END

Другие вопросы по теме