Препроцессор C заслуженно опасается и избегается сообществом C++. Встроенные функции, константы и шаблоны обычно являются более безопасной и превосходной альтернативой #define.
Следующий макрос:
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
никоим образом не превосходит безопасный тип:
inline bool succeeded(int hr) { return hr >= 0; }
Но у макросов есть свое место, пожалуйста, перечислите, какие применения вы найдете для макросов, которые вы используете не могу без препроцессора.
Пожалуйста, поместите каждый вариант использования в отдельный ответ, чтобы за него можно было проголосовать, и, если вы знаете, как получить один из ответов без препроцессора, укажите, как в комментариях этого ответа.
Однажды я взял приложение C++, полное макросов, на сборку которого ушло 45 минут, заменил макросы встроенными функциями и сократил сборку до менее 15 минут.
Тема посвящена контекстам, в которых макросы полезны, а не контекстам, в которых они неоптимальны.





Если вы хотите изменить поток программы (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.
Выброс исключения кажется мне лучшей альтернативой.
При написании расширений Python C (++) исключения распространяются путем установки строки исключения с последующим возвратом -1 или NULL. Таким образом, макрос может значительно сократить шаблонный код.
Часто я получаю такой код:
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.
Вы не можете выполнить короткое замыкание аргументов вызова функции, используя обычный вызов функции. Например:
#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
Может быть, более общий момент: функции оценивают свои аргументы только один раз. Макросы могут оценивать аргументы большее или меньшее количество раз.
@ [Greg Rogers] все, что делает препроцессор макроса, - это заменяет текст. Как только вы это поймете, в этом больше не должно быть никаких тайн.
Вы можете получить эквивалентное поведение, создав шаблон и f вместо того, чтобы заставлять оценку bool перед вызовом функции. Я бы не понял, что вы сказали правду, если бы не попробовал это на себе. Интересно.
Как именно вы могли сделать это с помощью шаблона?
Скрытие операций короткого замыкания за макросом стиля функции - одна из вещей, которую я действительно не хочу видеть в производственном коде.
Одно из распространенных применений - определение среды компиляции. Для кросс-платформенной разработки вы можете написать один набор кода, скажем, для Linux, а другой - для Windows, когда для ваших целей еще не существует кроссплатформенной библиотеки.
Итак, в грубом примере кроссплатформенный мьютекс может иметь
void lock()
{
#ifdef WIN32
EnterCriticalSection(...)
#endif
#ifdef POSIX
pthread_mutex_lock(...)
#endif
}
Для функций они полезны, когда вы хотите явно игнорировать безопасность типов. Например, во многих приведенных выше и ниже примерах выполнения ASSERT. Конечно, как и многие другие функции C / C++, вы можете выстрелить себе в ногу, но язык дает вам инструменты и позволяет вам решать, что делать.
Поскольку спрашивающий спросил: это можно сделать без макросов, включив разные заголовки через разные пути включения для каждой платформы. Хотя я склонен согласиться с тем, что макросы зачастую удобнее.
Я поддерживаю это. Если вы начнете использовать для этой цели макросы, код может быстро стать менее читаемым.
Очевидное включает охранников
#ifndef MYHEADER_H
#define MYHEADER_H
...
#endif
Если вы хотите сделать строку из выражения, лучшим примером для этого является assert (#x превращает значение x в строку).
#define ASSERT_THROW(condition) \
if (!(condition)) \
throw std::exception(#condition " is false");
Просто придирка, но я бы не стал ставить точку с запятой.
Я согласен, на самом деле я бы поставил его в do {} while (false) (чтобы предотвратить взлом), но я хотел, чтобы это было просто.
Методы всегда должны быть полным, компилируемым кодом; макросы могут быть фрагментами кода. Таким образом, вы можете определить макрос 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 определяет аргументы наоборот
Я должен с вами не согласиться - это может быть полезно для вас, но снизит читаемость вашего кода и приведет к проблемам, если другой человек попытается определить тот же макрос. В этом случае лучше просто придерживаться стандарта for ().
Кроме того, вам, вероятно, следует использовать итератор вместо индекса, поскольку std :: list :: size () должен будет каждый раз проходить весь список, поэтому было бы довольно неэффективно выполнять это сравнение в каждом цикле.
+1 Если вы используете какой-то смехотворно сложный синтаксис итератора, написание макроса в стиле foreach может значительно упростить чтение и сопровождение вашего кода. Я сделал это, работает.
Большинство комментариев совершенно неуместны до того, что макросы могут быть фрагментами кода, а не полным кодом. Но спасибо за придирки.
@jonner: предполагается, что компилятор не оптимизирует вызов метода для каждой итерации. Я предполагаю, что это сделает любой современный, не наивный компилятор.
@sqook: Как намекнул steveth45, я думаю, что идея цикла foreach сейчас достаточно повсеместна, поэтому должно быть очевидно, каковы ваши намерения.
Это C, а не C++. Если вы работаете на C++, вам следует использовать итераторы и std :: for_each.
Я не согласен, chrish. До лямбды for_each был неприятной вещью, потому что код, через который проходил каждый элемент, не был локальным для вызывающей точки. foreach (и я настоятельно рекомендую BOOST_FOREACH вместо ручного решения) позволяет держать код близко к месту итерации, что делает его более читабельным. Тем не менее, как только лямбда будет запущена, for_each снова может стать подходящим вариантом.
И стоит отметить, что BOOST_FOREACH сам по себе макрос (но очень хорошо продуманный)
А если вы используете C++ 11, вы можете просто использовать новую конструкцию foreach.
@chrish Надеюсь, что нет. Кто-то действительно напортачил, если реализовал свой интерфейс как указатели на функции в своей структуре list. В C++ есть функции-члены. C не делает. Тогда каждый объект списка будет нести несколько сотен байтов указателей на функции. Полная трата ценного (стекового) пространства.
Компиляторы могут отклонить ваш запрос на встраивание.
Макросы всегда будут на своем месте.
Что-то, что я считаю полезным, - это #define DEBUG для трассировки отладки - вы можете оставить его 1 на время отладки проблемы (или даже оставить его включенным в течение всего цикла разработки), а затем выключить, когда придет время для отправки.
Если компилятор отклоняет ваш запрос на встраивание, это может иметь очень вескую причину. Хороший компилятор будет лучше правильно встраивать, чем вы, а плохой вызовет больше проблем с производительностью, чем этот.
@DavidThornley Или это может быть не лучший оптимизирующий компилятор, такой как GCC или CLANG / LLVM. Некоторые компиляторы просто хрень.
Когда во время компиляции вы принимаете решение о специфическом поведении компилятора / ОС / оборудования.
Это позволяет вам сделать ваш интерфейс специфичным для 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
Фактически, исходный фрагмент: << ФАЙЛ ":" << в порядке, ФАЙЛ генерирует строковую константу, которая будет объединена с ":" в одну строку препроцессором.
Для этого требуется только препроцессор, потому что __FILE__ и __LINE__также требуют препроцессора. Использование их в вашем коде похоже на вектор заражения препроцессора.
Внутренняя условная компиляция, чтобы преодолеть проблемы различий между компиляторами:
#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>
Это удаляет #define, но не #ifdef и #endif. Во всяком случае, я с тобой согласен.
НИКОГДА не определяйте макросы нижнего регистра. Макросы для изменения функций - мой кошмар (спасибо Microsoft). Лучший пример - в первой строке. Многие библиотеки имеют функции или методы close. Затем, когда вы включаете заголовок этой библиотеки и заголовок с этим макросом, у вас возникает большая проблема, вы не можете использовать API библиотеки.
AndrewStein, видите ли вы какую-либо пользу от использования макросов в этом контексте по сравнению с предложением @paercebal? В противном случае кажется, что макросы на самом деле бесплатны.
@einpoklum - я давно это не писал. Предложение @paercebal удаляет #defines. В то время нам это было нужно как для C, так и для C++, так что ...
#ifdef WE_ARE_ON_WIN32 плз :)
Для защиты заголовочного файла требуются макросы.
Есть ли другие области, в которых используются макросы требовать? Немного (если есть).
Есть ли еще какие-то ситуации, в которых макросы могут быть полезны? ДА!!!
Одно место, где я использую макросы, - это повторяющийся код. Например, при упаковке кода 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, поэтому я сомневаюсь, что защита действительно необходима.
Они есть, если вы пишете для всех компиляторов, а не только для большинства ;-)
Итак, вместо переносимых стандартных функций препроцессора вы рекомендуете использовать расширение препроцессора, чтобы избежать использования препроцессора? Мне это кажется смешным.
#pragma once ломается во многих распространенных системах сборки.
Для этого есть решение, не требующее макросов: void handleExceptions(){ try { throw } catch (::mylib::exception& e) {....} catch (::std::exception& e) {...} ... }. А со стороны функций: void Foo(){ try {::mylib::Foo() } catch (...) {handleExceptions(); } }
Что-то вроде
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(), который выгружает информацию о файле / строке / функции? (во всяком случае, во всех реализациях, которые я видел)
Фреймворки модульного тестирования для C++, такие как UnitTest ++, в значительной степени вращаются вокруг макросов препроцессора. Несколько строк кода модульного теста превращаются в иерархию классов, которые было бы совсем не интересно печатать вручную. Я не знаю, как бы вы могли эффективно писать модульные тесты для C++ без чего-то вроде UnitTest ++ и его магии препроцессора.
Модульные тесты вполне можно писать без фреймворка. В конце концов, это действительно зависит только от того, какой результат вы хотите. Если вам все равно, подойдет простое значение выхода, указывающее на успех или неудачу.
Мы используем макросы __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.
Этот трюк не работает по стандарту. Он пытается создать маркер комментария через препроцессор, но комментарии должны быть удалены перед запуском препроцессора. Соответствующий компилятор вызовет здесь синтаксическую ошибку.
Извините, Дэвид, но компилятор должен содержать вторую копию удаления комментария.
гораздо проще сделать флаг отладки глобальной const bool и использовать такой код: if (debug) cout << "..."; - макросы не нужны!
@Stefan: В самом деле, это то, чем я сейчас занимаюсь. Любой достойный компилятор не будет генерировать код, если в этом случае отладка будет ложной.
Допустим, мы проигнорируем такие очевидные вещи, как защиту заголовка.
Иногда вам нужно сгенерировать код, который нужно скопировать / вставить прекомпилятор:
#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 только для определения сигнатуры файла, строки и функции и передать их функции (возможно, встроенной), которая сделает все остальное.
Обновите свой ответ, чтобы он отражал C++ 11 и обратился к комментарию @ RainerBlome.
@RainerBlome: Мы согласны. Макрос RAISE_ERROR_STL создан до C++ 11, поэтому в этом контексте он полностью оправдан. Насколько я понимаю (но у меня никогда не было возможности иметь дело с этими конкретными функциями), вы можете использовать вариативные шаблоны (или макросы?) В Modern C++, чтобы решить проблему более элегантно.
@einpoklum: «Пожалуйста, обновите свой ответ, чтобы он отражал C++ 11 и обратился к комментарию RainerBlome» № :-). . . Я считаю, что в лучшем случае я добавлю раздел для Modern C++ с альтернативными реализациями, уменьшающими или устраняющими необходимость в макросах, но суть в том, что макросы уродливы и злы, но когда вам нужно сделать что-то, чего компилятор не понимает , вы делаете это с помощью макросов.
Даже с 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; } Таким образом, макрос будет короче.
Вам нужны макросы для идентификаторов ресурсов в 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/…
Вы можете использовать #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 /…
Теперь, когда у нас есть constexpr в C++ 11, безопасную (не макро) версию также можно использовать в константном выражении. template<typename T, std::size_t size> constexpr std::size_t array_size(T const (&)[size]) { return size; }
Можете ли вы реализовать это как встроенную функцию?
#define my_free(x) do { free(x); x = NULL; } while (0)
template <class T> inline void destroy (T * & p) {delete p; р = 0; }
Константы #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.
Я считаю, что это неправильное отношение. То, что вы можете научиться «правильно с этим справляться», не означает, что это стоит чьего-либо времени и усилий.
По большей части:
__LINE__ и __FILE__)Ищете помощь о том, как реализовать номер 5. Можете ли вы помочь мне найти решение?
Вы можете включить дополнительное ведение журнала в отладочной сборке и отключить его для сборки выпуска без дополнительных затрат на логическую проверку. Итак, вместо:
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 не определен, это вообще не будет генерировать никакого кода. Ваша программа будет работать быстрее, и текст для журнала трассировки не будет компилироваться в ваш исполняемый файл.
Подобного эффекта можно добиться, играя с шаблонами.
встроенный void LogTrace (const char *) {if (DEBUG) doTrace (); } следует оптимизировать в выпускных сборках.
Строковые константы иногда лучше определять как макросы, поскольку со строковыми литералами можно больше, чем с 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 ++.
Это ситуация, которая побудила меня пожелать макросов на C#.
Хотел бы я +42 к этому. Очень важный, хотя и не часто вспоминаемый аспект строковых литералов.
Некоторые очень продвинутые и полезные вещи могут быть созданы с использованием препроцессора (макросов), чего вы никогда не сможете сделать, используя «языковые конструкции» C++, включая шаблоны.
Примеры:
Создание чего-либо как идентификатора C, так и строки
Простой способ использовать переменные перечислимого типа как строку в C
Ускорение метапрограммирования препроцессора
Третья ссылка не работает фуи
Взгляните на файлы stdio.h и sal.h в vc12, чтобы лучше понять.
Я использовал препроцессор для вычисления чисел с фиксированной точкой из значений с плавающей точкой, используемых во встроенных системах, которые не могут использовать плавающую точку в скомпилированном коде. Удобно иметь всю свою математику в единицах реального мира и не думать о них в фиксированной точке.
Пример:
// 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 )
Это можно сделать с помощью встроенной функции
Будут ли встроенные функции использовать другие встроенные функции и предотвращать попадание значений с плавающей запятой в окончательный код? Приведенный выше пример довольно прост, но я использовал этот метод для вычисления скорости вращения колеса через несколько шестерен с разными передаточными числами на основе числа оборотов двигателя. Макросы определяют каждый уровень преобразования.
Еще один макрос 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?
Это почти сработало бы в SQL ;-), но не в C, поскольку имя типа должно стоять перед именем переменной. Я думаю, вы могли бы скрыть это с помощью макро-сигнатуры вроде #define COLUMNS(name, from, to) name char[ (from) - (to) + 1 ].
Я использую макросы, чтобы легко определять исключения:
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: Я не согласен. Взгляните на ссылку
Макросы полезны для имитации синтаксиса операторов 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