Разыменование значения NULL
Рассмотрим одну из вариаций на тему умных указателей:
template <class Type>
class SPN {
private:
Type* pointer;
public:
SPN() : pointer(NULL) {}
SPN(Type* p) : pointer(p) {}
operator Type*() { return pointer; }
Type* operator->()
{
if (pointer == NULL) {
cerr << "Dereferencing NULL!" << endl;
pointer = new Type;
}
return pointer;
}
При попытке вызвать оператор -> для указателя pointer, равного NULL, в поток stderr выводится сообщение об ошибке, после чего создается фиктивный объект и умный указатель переводится на него, чтобы программа могла хромать дальше.
Существует столько разных решений, сколько найдется программистов, достаточно глупых для попыток разыменования значения NULL. Вот лишь несколько из них.
Использование #indef Если вас раздражают дополнительные вычисления, связанные с этой логикой, проще всего окружить if-блок директивами #ifdef, чтобы код обработки ошибок генерировался только в отладочных версиях программы. При компиляции рабочей версии перегруженный оператор -> снова сравнивается по быстродействию со встроенным указателем.
Инициирование исключений
Выдача сообщений об ошибках может вызвать проблемы в некоторых графических программах. Вместо этого можно инициировать исключение:
template <class Type>
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 Type>
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;
}
};
«Ну и что такого?» - спросите вы. Предположим, screamer на самом деле не принадлежит к типу Type* а относится к производному классу, все функции которого (предположительно виртуальные) выводят сообщения об ошибках в поток сеrr перед вызовом своих прототипов базового класса. Теперь вы не только удержите свою программу на плаву, но и сможете следить за попытками вызова функций фиктивного объекта.