Анимация
JavaScript
|
Главная Библионтека public: virtual ~MP() {} Освобождение выполняется производными классами virtual Type* operator->() const = 0; template <c1ass Type> class DefaultMP : public MP<Type> { private: Type* pointee; public: Defau1tMP() : pointee(new Type) {} Defau1tMP(const Defau1tMP<Type>& dmp) : pointee(new Type(*dmp.pointee)) {} virtual ~Defau1tMP() { delete pointee; } Defau1tMP<Type>& operator=(const Defau1tMP<Type>& dmp) if (this == &dmp) return *this; delete pointee; pointee = new Type(*dmp.pointee); return *this; virtual Type* operator->() const { return pointee; } template <c1ass Type> class LocalPoolMP : public MP<Type> { private: Type* pointee; Pool* pool; public: LocalPoolMP(Poo1* p) : pointee(new(p) Type), poo1(p) [] LocalPoolMP(const LocalPoolMP<Type>& Ipmp) : pointee(new(1pmp.poo1) Type(*1pmp.pointee)), pool(lpmp.pool) {} virtual ~LocalPoolMP() { pointee->Type::~Type(); } LocalPoolMP<Type>& operator=(const LocalPoolMP<Type>& Ipmp) if (this == &1pmp) return *this; pointee->Type::~Type(); pointee = new(pool) Type(*1pmp.pointee); return *this; virtual Type* operator->() const { return pointee; } Теперь DefaultMP и LocalPoolMP можно использовать совместно - достаточно сообщить клиенту, что они принадлежат к типу MP<Type>&. Копирование и присваивание поддерживается для тех классов, которые взаимодействуют с производными классами, но запрещено для тех, которые знают только о базовом классе. В приведенном коде есть одна тонкость: операторная функция LocalPoolMP::operator= всегда использует new(pool) вместо new(lpmp.pool). Это повышает безопасность в тех ситуациях, когда два ведущих указателя поступают из разных областей действия и разных пулов. Невидимые указатели Раз уж мы «заплатили вступительный взнос» и создали иерархию классов ведущих указателей, почему бы не пойти дальше и не сделать эти указатели невидимыми? Вместо применения шаблона нам придется реализовать отдельный класс указателя для каждого класса указываемого объекта, но это не слишком большая цена за получаемую гибкость. В файле foo.h class Foo { public: static Foo* make(); Использует выделение по умолчанию static Foo* make(Poo1*); Использует пул virtual ~Foo() {} Далее следуют чисто виртуальные функции В файле foo.cpp class PoolFoo : public Foo { private: Foo* foo; Pool* pool; public: PoolFoo(Foo* f, Pool* p) : foo(f), poo1(p) {} virtual ~PoolFoo() { foo->~Foo(); } Переопределения функций класса, делегирующие к foo class PFoo : public Foo { Обычный невидимый указатель class ConcreteFoo : public Foo { ... }; Foo* Foo::make() return new PFoo(new ConcreteFoo); Foo* Foo::make(Poo1* p) return new PoolFoo(new(p) ConcreteFoo, p); Такой вариант намного «чище» для клиента. Единственное место, в котором клиентский код должен знать что-то о пулах, - создание объекта функцией make(Poo1*). Остальные пользователи полученного невидимого указателя понятия не имеют, находится их рабочий объект в пуле или нет. Стековые оболочки Чтобы добиться максимальной инкапсуляции, следует внести в описанную архитектуру следующие изменения: • Сделать Pool чисто абстрактным базовым классом с инкапсулированными производными классами, производящими функциями и т.д. • Предоставить функцию static Foo::makePoo1(). Функция make(Poo1*) будет работать и для других разновидностей Pool, но makePoo1() позволяет Foo выбрать производящую функцию Pool, оптимальную для хранения Foo (например, с передачей размера экземпляра). • Переработать старый шаблон MP из предыдущих глав (с операторной функцией operator Type*()), чтобы при выходе из пула и указателей за пределы области действия все необходимое автоматически уничтожалось. Ниже показан примерный вид полученного интерфейса, с фрагментом клиентского кода и без виртуального оператора =. В файле foo.h Подключить объявление чисто абстрактного базового класса #inc1ude "pool.h" class Foo { private: Foo(const Foo&) {} Foo& operator=(const Foo&) { return *this; } public: static Pool* makePoo1(); Создать пул, оптимизированный для Foo static Foo* make(); Не использует пул static Foo* make(Poo1*); Использует пул И т.д. Клиентский код void g(Foo*); void f() { MP<Poo1> poo1(Foo::makePoo1()); MP<Foo> foo(Foo::make(poo1)); foo->MemberOfFoo(); Использует операторную функцию operator->() g(foo); Использует операторную функцию operator Type*() Выход из области действия - удаляется сначала foo, затем pool Перспективы Глава заканчивается хорошо - умной, эффективной инкапсуляцией очень сложной проблемы дизайна. Единственным уязвимым местом является вызов функции g() , которая должна пообещать не сохранять долговременный указатель на свой аргумент. Впрочем, подобный анализ необходимо проводить для любой архитектуры, в которой используются временные пулы; в нашем случае ключом является инкапсуляция. На время забудьте о пулах, временных или иных, и вы увидите разнообразные стратегии применения ведущих указателей для поддержки и инкапсуляции управления памятью в С++. 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 |