Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [ 22 ] 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

Существует столько разных решений, сколько найдется программистов, достаточно глупых для попыток разыменования значения NULL. Вот лишь несколько из них.

Использование #indef

Если вас раздражают дополнительные вычисления, связанные с этой логикой, проще всего окружить if-блок директивами #ifdef, чтобы код обработки ошибок генерировался только в отладочных версиях программы. При компиляции рабочей версии перегруженный оператор -> снова сравнивается по быстродействию со встроенным указателем.

Инициирование исключений

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

template <c1ass 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 <c1ass 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 перед вызовом своих прототипов базового класса. Теперь вы не только удержите свою программу на плаву, но и сможете следить за попытками вызова функций фиктивного объекта.

Отладка и трассировка

Умные указатели также могут использоваться для наблюдения за объектами, на которые они указывают. Поскольку все обращения к объекту выполняются через операторную функцию operator Type*() или operator->(), у вас появляются две контрольные точки для наблюдения за происходящим во время работы программы. Возможности отладки безграничны, я приведу лишь один из примеров.

Установка точек прерывания

Самое простое применение упомянутых контрольных точек - сделать эти функции вынесенными (out-of-line) в отладочной версии и расставить точки прерывания в их реализации.

template <c1ass Type> class PTracer { private:

Type* pointer; public:

PTracer() : pointer(NULL) {} PTracer(Type* p) : pointer(p) {} operator Type*(); Type* operator->();

template <c1ass Type> #ifdef DEBUGGING inline #endif

PTracer<Type>::operator Type*()

return pointer; Здесь устанавливается точка прерывания

template <c1ass Type> #ifdef DEBUGGING inline

#endif

Type* PTracer<Type>::operator->()

return pointer; Здесь устанавливается точка прерывания

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

Трассировка

Оператор преобразования и оператор -> могут выводить диагностическую информацию в поток cout или cerr, в зависимости от ситуации.



Ведение статистики класса

Также несложно организовать накопление статистики об использовании операторов Type* и -> в статических переменных параметризованного класса.

template <c1ass Type> class SPCS { private:

Type* pointer;

static int conversions;

static int members; public:

SPCS() : pointer(NULL) {}

SPCS(Type* p) : pointer(p) {}

operator Type*() { conversions++; return pointer; } Type* operator->() { members++; return pointer; } int Conversions() { return conversions; } int Members() { return members; }

Глобальные переменные должны быть где-то определены. Обычно это делается в файле Foo.cpp: В файле Foo.cpp int Ptr<Foo>::conversions = 0; int Ptr<Foo>::members = 0;

Разумеется, вы можете воспользоваться директивами #ifdef, чтобы это относилось только к отладочной версии.

Ведение статистики объекта

Мы подошли к более сложной теме. Возможно, ее следует отложить до знакомства с ведущими указателями (master pointers), однако умные указатели также могут вести статистику по отдельным объектам, а не по классу в целом. Задача не сводится к тому, чтобы сделать только что показанные переменные нестатическими (то есть по одному экземпляру переменных на указатель), поскольку мы (пока) не можем обеспечить однозначное соответствие между указателями и объектами. Вместо этого статистику придется хранить в самих объектах. Ниже приведен полезный вспомогательный класс, который можно создать на основе множественного наследования как производный от класса указываемого объекта и от класса умного указателя, знающего о его свойствах. Объявляя указатель другом, вы предоставляете ему доступ к защищенным членам классов, производных от Counter.

class Counter {

protected:

Counter() : conversions(O), members(O) {} Counter(const Counter&) : conversions(O), members(O) {} Counter& operator=(const Counter&) { return *this; } public:

int conversions; int members;

int Conversions() { return conversions; } int Members() { return members; }

template <c1ass Type> class SPOP { private:

Type* pointer;



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [ 22 ] 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82