Если все альтернативы отпали, то используйте препроцессор
Мы увидим в главе, посвященной Си++, что препроцессор Си не играет большой роли в Си++. Хотя есть немного мест, где он все еще кстати. Вот первое из них:
#ifdef DEBUG
# define
D(x) x
#else
# define D(X) /*
пусто */
#endif
Вместо макроса D()
подставляется его аргумент, если вы занимаетесь отладкой, иначе он расширяется до пустой строки. Он используется так:
f()
{
D( printf("Это отладочная информация\n"); )
}
В данном случае аргументом D()
является целиком оператор printf(), который исчезает после того, как вы закончите отладку.
Другой подобный вариант использования кстати, когда вы должны инициализировать те несколько неизбежных глобальных переменных в большой программе. Проблема заключается в синхронизации объявлений переменных (в заголовочном файле) с их определениями (в файле .c), где реально выделяется память и переменные инициализируются. Вот образец заголовочного файла:
#ifdef ALLOC
# define I(x) x
# define EXPORTED /* пусто
*/
#else
# define I(x) /* пусто
*/
# define EXPORTED extern
#endif
EXPORTED int glob_x[10] I( ={1, 2, 3, 4} );
EXPORTED some_object glob_y I( ("конструктор", "аргументы"));
В определенном месте своей программы (я обычно делаю это в файле с именем globals.cpp) вы помещаете следующие строки:
#define ALLOC
#include "globals.h"
Далее везде вы просто включаете этот файл без предварительной директивы #define ALLOC. Когда вы компилируете
globals.cpp, директива #define ALLOC
вызывает следующую подстановку:
/* пусто */ int glob_x[10] ={1, 2, 3, 4} ;
/* пусто */ some_object glob_y ("конструктор", "аргументы");
Отсутствие #define ALLOC
везде приводит к следующей подстановке:
extern int glob_x[10] /* пусто
*/ ;
extern some_object glob_y /* пусто
*/ ;
Последним примером использования препроцессора будет макрос ASSERT(), который выводит сообщение об ошибке и завершает программу, лишь если вы осуществляете отладку (директивой #define определена константа DEBUG) и аргумент ASSERT()
имеет значение "ложь". Он очень удобен для тестирования, например, аргументов типа указателей со значением NULL. Вариант ASSERT(), используемый в виде:
f( char *p)
{
ASSERT( p, "f() : Неожиданный аргумент NULL." );
}
определяется следующим образом:
#ifdef DEBUG
#define ASSERT(условие, сообщение)
if ( !(условие) ) \
{\
fprintf(stderr, "ASSERT(" #условие ") НЕ ВЫПОЛНЕНО "\
"[Файл " __FILE__ ", Строка %d]:\n\t%s\n",\
__LINE__, (сообщение) );\
exit( -1 );\
}\
else
#else
# efine ASSERT(c,m) /* пусто */
#endif
В вышеуказанном примере ASSERT()
выводит следующую строку при отрицательном результате проверки:
ASSERT(p) НЕ ВЫПОЛНЕНО [Файл whatever.cpp, Строка 123]:
f() : Неожиданный аргумент NULL.
и затем выходит в вызывающую программу. Он получает текущее имя файла и номер строки от препроцессора, используя предопределенные макросы __FILE__ и __LINE__. Условие, вызывающее отрицательный результат, выводится посредством оператора получения строки ANSI Си (символ #), который фактически окружает расширенный аргумент кавычками после выполнения подстановки аргумента. Строка #условие
расширяется до "p" в настоящем примере). Затем вступает в действие обычная конкатенация строк Си для слияния вместе разных строк, создавая единый отформатированный аргумент для fprintf().
Здесь следует
использовать препроцессор, потому что вам нужно вывести на консоль имя файла и номер строки, для которых выполнена проверка. Встроенная функция Си++ может вывести лишь имя того файла с номером строки, в котором определена встроенная функция.
Все компиляторы, поддерживающие стандарт ANSI Си, должны реализовывать макрос assert(expr) в заголовочном файле assert.h, но макрос ANSI Си не может выводить заказанное сообщение об ошибке. Макрос ANSI Си
assert()
действует, если не определена константа NDEBUG
(вариант по умолчанию).