2008-09-18 3 views
145

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

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

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

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

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

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

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

ответ

112

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

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

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

Часто я заканчиваю с кодом, как:

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) { ... } 

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

+8

В этой ситуации вам может быть лучше создать структуру CallbackArg. – 2008-09-19 04:24:24

17

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

#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 
+2

Возможно, более общий момент: функции оценивают свои аргументы ровно один раз. Макросы могут оценивать аргументы больше раз или меньше раз. – 2008-09-18 20:06:28

+0

@ [Грег Роджерс] все препроцессор макросов является заменяющим текстом. Как только вы это понимаете, в этом не должно быть больше тайны. – 2008-09-18 20:08:47

+0

Вы можете получить эквивалентное поведение путем templatizing andf вместо принудительного вычисления оценки до вызова функции. Я бы не понял, что вы сказали, правда, не пытаясь это для себя. Интересно. – 2008-09-18 20:11:42

6

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

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

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

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

+0

Поскольку вопросник спросил: это можно сделать без макросов, включив разные заголовки через разные пути включения на платформу. Я склонен согласиться, хотя макросы часто более удобны. – 2008-09-18 19:59:41

+0

I второй это. Если вы начнете использовать макросы для этой цели, код может быстро стать менее читаемым. – 2008-09-18 20:38:44

19

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

#ifndef MYHEADER_H 
#define MYHEADER_H 

... 

#endif 
36

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

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

Просто nitpick, но я лично оставил бы точку с запятой. – 2008-09-18 21:05:43

+9

Я согласен, на самом деле, я бы поставил его в {{while) (false) (чтобы не допустить лишний раз), но я хотел сохранить его простым. – Motti 2008-10-05 18:24:23

82

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

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

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

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

Поскольку C++ 11, это сменяется range-based for loop.

+1

Ницца - но осторожный, BOOST foreach определяет args наоборот – 2008-10-03 02:37:18

+0

Я должен не согласиться с вами здесь - это может быть полезно для вас, но уменьшит читаемость вашего кода и приведет к проблемам, если другой человек попытается определить тот же самый макро. Лучше просто придерживаться стандарта для() в этом случае. – 2008-10-05 20:00:04

+1

, вы также должны использовать итератор вместо индекса, так как std :: list :: size() будет каждый раз ходить по всему списку, поэтому для выполнения этого сравнения в каждом цикле может быть довольно неэффективно. – jonner 2008-10-08 00:29:08

2

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

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

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

+9

Если компилятор отказывается от вашего запроса на inline, у него может быть очень веская причина. Хороший компилятор будет лучше встраивать правильно, чем вы, а плохой даст вам больше проблем с производительностью, чем это. – 2008-10-23 19:22:19

2

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

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

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

#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 
+11

В C++ то же самое можно было бы получить с помощью встроенных функций: #ifdef ARE_WE_ON_WIN32
inline int close(int i) { return _close(i) ; }
#endif
paercebal 2008-10-22 22:31:21

+2

Это удаляет # define, но не #ifdef и #endif. В любом случае, я согласен с вами. – Gorpik 2008-10-23 08:16:54

49

Хранители файлов заголовков требуют макросов.

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

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

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

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

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

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

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

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

+5

Большинство компиляторов поддерживают `#pragma once` в эти дни, поэтому я сомневаюсь, что наборы действительно нужны – 2008-09-18 20:10:30

+13

Они, если вы пишете для всех компиляторов вместо большинства ;-) – 2008-09-18 20:13:45

6

Что-то вроде

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

Так что вы можете просто иметь, например,

assert(n == true); 

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

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

void assert(bool val); 

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

16

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

9

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

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

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

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

1

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

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

+2

Этот трюк не работает в соответствии со стандартом. Он пытается создать маркер комментария через препроцессор, но комментарии должны быть удалены до запуска препроцессора. Соответствующий компилятор вызовет здесь синтаксическую ошибку. – 2008-10-23 19:20:55

15

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

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

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

(Примечание: код копировать/вставить из повышающего страницы)

Который (имхо) лучше, чем std::for_each.

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

0

Вам нужны макросы для идентификаторов ресурсов в Visual Studio, поскольку компилятор ресурсов только их понимает (т. Е. Он не работает с константой или перечислением).

7

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

Например, в поле «field_list».ч ":

/* 
* 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" 
+1

Примечание: подобная техника может быть реализована даже без отдельного включения. См .: http://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c#202511 http://stackoverflow.com/questions/126277/make-something-both-ac-identifier-and-a-string # 126511 – Suma 2008-10-23 21:54:07

3

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

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

Компиляция вам код с помощью:

gcc -Imemlog_preinclude.h ... 

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

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

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

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

Вы можете реализовать это в качестве встроенной функции?

#define my_free(x) do { free(x); x = NULL; } while (0) 
+13

template inline void destroy (T * & p) {delete p; p = 0; } – KTC 2008-09-19 04:48:01

+0

@KTC: Так правильно ... Жаль, что вы не можете перемотать комментарий ... :-p ... – paercebal 2008-10-22 22:35:11

1

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

2

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

#define dbgmsg(_FORMAT, ...) if((debugmsg_flag & 0x00000001) || (debugmsg_flag & 0x80000000))  { log_dbgmsg(_FORMAT, __VA_ARGS__); } 

из-за va_args в функциях журнала, это был хороший случай для макроса, как это.

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

макросъемки (s) определяется как:

#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return 
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false)) 

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

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

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

1

Если у вас есть список полей, которые используются для кучи вещей, например. определяя структуру, сериализуя эту структуру в/из некоторого двоичного формата, делая вставки базы данных и т. д., тогда вы можете (рекурсивно!) использовать препроцессор, чтобы избежать повторения списка полей.

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

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

(EDIT: Теперь я вижу, что это похоже на то, что @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) 
12

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

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

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

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

44

В основном:

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

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

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

+1

Можно добиться подобного эффекта, играя с шаблонами. – 2008-11-28 17:11:32

26

Строковые константы иногда лучше определяются как макросы, поскольку вы можете делать больше с строковыми литералами, чем с const char *.

например. Строковые литералы могут быть easily concatenated.

#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

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

Примеры:

Making something both a C identifier and a string

Easy way to use variables of enum types as string in C

Boost Preprocessor Metaprogramming

1

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

Пример:

// 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) 
2

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

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

Usage (концепция, показывающая, не реальный):

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 (Eric Niebler) http://www.artima.com/cppsource/foreach.html

2

Возможно, использование макросов 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 

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

0
#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); 
}; 
2

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

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

Кажется 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, мы можем решить эту проблему «правильно» с помощью шаблона.

7

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

Посмотрите на boost preprocessor library, это своего рода мета-мета-программирование. В теме-> мотивации вы можете найти хороший пример.

Смежные вопросы