Когда макросы С++ полезны?
Препроцессор 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"
Ответ 17
Повторение кода.
Посмотрите повысить препроцессорную библиотеку, это своего рода мета-мета-программирование. В теме- > мотивации вы можете найти хороший пример.
Ответ 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 файлы на какие константы определены для каждой платформы.