Вопрос

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

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

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

ни в коей мере не превосходит тип safe:

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

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

Защита заголовочного файла требует использования макросов.

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

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

Одно место, где я использую макросы, - это с очень повторяющимся кодом.Например, при переносе кода 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__ макросы препроцессора.

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

В основном:

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

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

#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

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

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

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

Если a 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);

Когда вы хотите изменить ход выполнения программы (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.

К очевидным относятся охранники

#ifndef MYHEADER_H
#define MYHEADER_H

...

#endif

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

#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

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

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

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

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

Что (ИМХО) намного лучше, чем std::for_each.

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

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

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

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

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

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

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

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

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

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

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

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

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

Примеры:

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

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

Метапрограммирование ускоряющего препроцессора

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

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

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

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

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

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

Что -то вроде

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, напечатанный в журнале, что было бы менее полезно.

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

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

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

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

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

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

gcc -Imemlog_preinclude.h ...

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

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

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

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

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

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

Когда вы принимаете решение во время компиляции по поводу поведения, зависящего от компилятора / операционной системы / аппаратного обеспечения.

Это позволяет вам адаптировать свой интерфейс к специфичным функциям компилятора / операционной системы / оборудования.

#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

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

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

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

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

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

Доступные хорошие статьи: Условная Любовь:ПЕРЕД КАЖДЫМ Повторением (Эрик Ниблер) http://www.artima.com/cppsource/foreach.html

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

typedef ...some struct

--POSIX_TYPES.h

typedef ...some another struct

--программа.h

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

#include TYPES_H

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

Похоже, что 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);                  \
  }                                                      \
/**/

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top