Когда полезны макросы C ++?[закрыто]
-
01-07-2019 - |
Вопрос
Тот Самый 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__
макросы препроцессора.
В любом случае, макросы очень полезны при правильном использовании.Макросы не являются злом - их неправильное использование это зло.
В основном:
- Включать охранников
- Условная компиляция
- Отчетность (предопределенные макросы, такие как
__LINE__
и__FILE__
) - (редко) Дублирование повторяющихся шаблонов кода.
- В коде вашего конкурента.
Внутри условной компиляции для преодоления проблем, связанных с различиями между компиляторами:
#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 определять, какие константы определены для каждой платформы.