Bog'liq C -Eldjer-Djeff-for-Real-Programmers-RUS-www.itlibitum.ru
79 Существует столько разных решений, сколько найдется программистов, достаточно глупых для
попыток разыменования значения
NULL
. Вот лишь несколько из них.
Использование #indef
Если вас раздражают дополнительные вычисления, связанные с этой логикой, проще всего окружить
if
-блок директивами
#ifdef
, чтобы код обработки ошибок генерировался только в отладочных
версиях программы. При компиляции рабочей версии перегруженный оператор ->
снова сравнивается
по быстродействию со встроенным указателем.
Инициирование исключений
Выдача сообщений об ошибках может вызвать проблемы в некоторых графических программах.
Вместо этого можно инициировать исключение:
template class Ptr {
private:
Type*
pointer;
public:
enum ErrorType { DereferenceNil };
Ptr() : pointer(NULL) {}
Ptr(Type* p) : pointer(p) {}
operator Type*() { return pointer; }
Type*
operator->()
throw(ErrorType)
{
if (pointer == NULL) throw DereferenceNil;
return
pointer;
}
};
(На практике
ErrorType
заменяется глобальным типом, используемым для различных видов ошибок;
приведенный фрагмент лишь демонстрирует общий принцип.) Это решение может объединяться с
другими. Например, программа может использовать фиктивный объект в отладочном варианте и
инициировать исключение в рабочей версии.
Стукачи
Еще один вариант — хранить в статической переменной специальный объект, который я называю
«стукачом» (screamer). Стукач ждет, пока кто-нибудь не попытается выполнить разыменование
значения
NULL
.
template class AHHH {
private:
Type*
pointer;
static type* screamer;
public:
AHHH() : pointer(NULL) {}
AHHH(Type* p) : pointer(p) {}
Operator Type*() { return pointer; }
Type*
operator->()
{
if (p == NULL) return screamer;
return
pointer;
80 }
};
«Ну и что такого?» — спросите вы. Предположим,
screamer
на самом деле не принадлежит к типу
Type*
а относится к производному классу, все функции которого (предположительно виртуальные)
выводят сообщения об ошибках в поток
сеrr
перед вызовом своих прототипов базового класса. Теперь
вы не только удержите свою программу на плаву, но и сможете следить за попытками вызова функций
фиктивного объекта.
Отладка и трассировка
Умные указатели также могут использоваться для наблюдения за объектами, на которые они
указывают. Поскольку все обращения к объекту выполняются через операторную функцию
operator
Type*()
или
operator->()
, у вас появляются две контрольные точки для наблюдения за
происходящим во время работы программы. Возможности отладки безграничны, я приведу лишь один
из примеров.
Установка точек прерывания
Самое простое применение упомянутых контрольных точек — сделать эти функции вынесенными (out-
of-line) в отладочной версии и расставить точки прерывания в их реализации.
template class PTracer {
private:
Type*
pointer;
public:
PTracer() : pointer(NULL) {}
PTracer(Type* p) : pointer(p) {}
operator
Type*();
Type*
operator->();
};
template #ifdef DEBUGGING
inline
#endif
PTracer::operator Type*()
{
return
pointer;
//
Здесь устанавливается точка прерывания
}
template #ifdef DEBUGGING
inline
#endif
Type* PTracer::operator->()
{
return
pointer;
//
Здесь устанавливается точка прерывания
}
С непараметризованными версиями указателей это сделать несколько проще, поскольку не все среды
разработки позволяют устанавливать точки прерывания в параметризованных функциях.
Трассировка
Оператор преобразования и оператор
->
могут выводить диагностическую информацию в поток
cout
или
cerr
, в зависимости от ситуации.