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

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

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

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

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

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

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

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

Ответ 1

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

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

Ответ 2

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

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

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

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

Так как С++ 11, это заменяется на цикл для цикла.

Ответ 3

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

#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

Ответ 4

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

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

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

Одно место, где я использую макросы, - это очень повторяющийся код. Например, при переносе кода на С++, который будет использоваться с другими интерфейсами (.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__); \
}

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

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

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

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

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

Ответ 5

В основном:

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

Ответ 6

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

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

Ответ 7

Строковые константы иногда лучше определяются как макросы, поскольку вы можете делать больше с строковыми литералами, чем с 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);

Ответ 8

Если вы хотите изменить поток программы (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 another discussion.

Ответ 9

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

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

Ответ 10

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

#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

Ответ 11

Unit test рамки для С++, такие как UnitTest ++ в значительной степени вращаются вокруг макросов препроцессора. Несколько строк кода unit test расширяются в иерархию классов, которые не будут забавно вводить вручную. Без чего-то вроде UnitTest ++ и магии препроцессора я не знаю, как бы вы эффективно записывали модульные тесты для С++.

Ответ 12

Скажем, мы будем игнорировать очевидные вещи, такие как защита заголовков.

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

#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 С++:

#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, никогда не переведенным на правильный С++.

Ответ 13

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

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

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

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

Ответ 14

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

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

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

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

Ответ 15

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

Примеры:

Создание как идентификатора C, так и строки

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

Повысить метапрограммирование препроцессора

Ответ 16

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

Например, в поле "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;

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

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

#include "field_list.h"

Ответ 18

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

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

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

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

Ответ 19

Что-то вроде

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

Чтобы вы могли, например, иметь

assert(n == true);

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

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

void assert(bool val);

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

Ответ 20

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

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

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

Ответ 21

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

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

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

gcc -Imemlog_preinclude.h ...

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

Ответ 22

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

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

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

Ответ 23

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

Это позволяет вам настроить свой интерфейс на специальные функции 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

Ответ 24

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

#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;
   }
...

В любом случае, как я их использовал, и я не уверен, как это могло быть помогло с помощью шаблонов... Кроме этого, я стараюсь избегать их, если это НЕОБХОДИМО.

Ответ 25

Еще один макрос foreach. T: type, c: container, i: iterator

#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

Ответ 26

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

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

Ответ 27

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

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;\
    };\
  }\

Ответ 28

Похоже, что VA_ARGS косвенно упоминались до сих пор:

При написании общего кода С++ 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);                  \
  }                                                      \
/**/

Примечание: В общем, проверка имени/бросок также может быть включена в гипотетическую функцию get_op_from_name. Это просто пример. Может существовать другой общий код, окружающий вызов VA_ARGS.

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

Ответ 29

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

#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.

Ответ 30

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