Анимация
JavaScript
|
Главная Библионтека Например, ниже приведена реализация классов стратегий NoChecking и Enfor-ceNotNull. template <class т> struct NoChecking static void Check(T*) {} template <class т> struct EnforceNotNull { class NullPointerException : public std::exception { ... } static void Check(T* ptr) if C!ptr) throw NullPointerExceptionC); Подключая разные классы стратегии проверки, можно реализовать различные режимы работы объектов. Ниже показано, что можно даже инициализировать объект, на который ссылается указатель, значением, заданным по умолчанию, получив ссылку на указатель. template <class т> struct EnsureNotNul1 { static void Check(T*& ptr) { if C!ptr) ptr = GetDefaultValueC); Класс SmartPtr использует стратегию Checking следующим образом. template < class T, template <class> class CheckingPolicy, template <class> class ThreadingModel > class SmartPtr : public CheckingPolicy<T>, public ThreadingModel<SmartPtr> T* operator->() { typename ThreadingModel<SmartPtr>::Lock guardC*this); Checki ngPoli cy<T>::Check(poi ntee ) return pointee ; private: T* pointee ; Отметим, что оба класса стратегий CheckingPolicy и ThreadingModel используют одни и те же функции. Функционирование оператора SmartPtr: :operator-> зависит от двух шаблонных аргументов. В этом проявляется сила, присущая комбинированию стратегий. / Разложив класс на ортогональные стратегии, можно обеспечить широкий спектр режимов работы с помощью небольшого по объему кода. 1.10. Настройка структур с помощью классов стратегий Одно из офаничений, присущих шаблонам, как было указано в разделе 1.4, состоит в том, что с помощью шаблонов невозможно уточнить структуру класса - только его режим работы. В то же время проектирование, основанное на использовании стратегий, позволяет уточнить структуру вполне естественным образом. Допустим, что нам нужно создать класс SmartPtr без применения указателей. Например, на отдельных платформах некоторые указатели могут быть представлены особым образом - в виде целого числа, которое передается системной функции, возвращающей настоящий указатель. Для того чтобы решить эту задачу, можно определить доступ к указателю, например, с помощью стратегии Structure. Эта стратегия абстрагирует способ хранения указателя. Следовательно, в этой стратегии нужно указать типы с именем PointerType (тип указателя) и ReferenceType (тип ссылки). То, что тип указателя не задан жестко как т*, представляет собой большое преимущество. Например, можно использовать класс SmartPtr с нестандартными типами указателей (такими, как указатели near и far, применяющиеся на компьютерах, имеющих сегментированную архитектуру) или легко реализовать такие остроумные решения, как функции before и after (Stroustoip, 2000а). Эти возможности чрезвычайно интересны. По умолчанию интеллектуальный указатель хранится как простой указатель, снабженный интерфейсом стратегии Structure. template <class т> class DefaultSmartPtrStorage public: typedef T* PointerType; typedef T& ReferenceType; protected: PointerType GetPointerC) { return ptr ; } void setPointerCPointerType ptr) { pointee = ptr; } private: PointerType ptr ; Фактический способ хранения полностью скрыт за фасадом интерфейса стратегии Structure. Теперь класс Smartptr может использовать стратегию хранения указателя Storage, а не фуппироваться с типом т*. template < class т, template <class> class CheckPolicy, template <class> class ThreadingModel, template <class> class Storage = DefaultSmartPtrStorage > class SmartPtr; Разумеется, для того, чтобы внедриться в нужную структуру, класс SmartPtr должен либо быть производным от класса Storage<T>, либо группироваться с объектом типа Storage<T>. 1.11. Совместимые и несовместимые стратегии Допустим, нам нужны две конкретизации класса SmartPtr: FastwidgetPtr, указатель без проверки, и SafewidgetPtr, указатель с проверкой перед разыменованием. Возникает интересный вопрос: нужно ли присваивать объекты класса FastwidgetPtr объектам класса SafewidgetPtr? Следует ли присваивать объекты этих классов другим переменным? Если такие преобразования необходимы, как их реализовать? Отталкиваясь от той точки зрения, что указатели типа SafewidgetPtr накладывают больше ограничений, чем указатели типа FastwidgetPtr, естественно допустить преобразование типа FastwidgetPtr в тип SafewidgetPtr. Причина этого заключается в том, что язык С++ уже поддерживает неявные преобразования, увеличивающие ограничения, например, переменных неконстантных типов - в константные. С другой стороны, свободное преобразование объектов класса SafewidgetPtr в объекты класса FastwidgetPtr опасно, поскольку в приложениях основная масса кода может использовать указатели типа SafewidgetPtr, и только небольшое ядро, для которого скорость выполнения является крайне важной, может работать с указателями типа FastwidgetPtr. Разрешая только явные, контролируемые преобразования в тип FastwidgetPtr, можно свести к минимуму использование указателей этого типа. Наилучшим и наиболее масштабируемым способом взаимного преобразования стратегий является инициализация и копирование объектов класса SmartPtr стратегия за стратегией (policy by policy), как показано ниже. (Для простоты рассмотрена только одна стратегия - Checking.) template < class т, template <class> class CheckingPoliсу > class SmartPtr : public CheckingPolicy<T> { template < class Tl, template <class> class CPl > SmartPtrCconst SmartPtr<Tl, CPl>& other) : pointee (other.pointee ), CheckingPolicy<T>(other) { ... } Класс SmartPtr реализует шаблонную копию конструктора, получающую другую конкретизацию класса SmartPtr. Код, вьщеленный полужирным шрифтом, инициализирует компоненты класса SmartPtr компонентами другого класса SmartPtr<Tl, CPl>, полученными в виде аргументов. Вот как работает этот способ. (Проследим за выполнением кода конструктора.) Допустим, у нас есть класс ExtendedWidget, производный от класса Widget. Если объект класса SmartPtr<Widget, NoChecking> проинициализировать объектом класса SmartPtr<ExtendedWidget, NoChecking>, компилятор попытается проинициализировать указатель widget* указателем ExtendedWidget* (что вполне допустимо), а объект класса NoChecking- объектом класса SmartPtrExtended<Extendedwidget, NoCheck-ing>. Возможно, это выглядит подозрительно, но не забывайте, что класс SmartPtr является производным от своих стратегий, так что, по существу, компилятор может легко распознать, что мы инициализируем объект класса NoChecking другим объектом того же класса. Таким образом, процесс инициализации выполняется без проблем. 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |