С++ - язык, который изучается постепенно.ГЛАВА 13. Управление памятью с применением ведущих указателей
                   Справочники Всё для создания сайта

Ссылки


Home
Бизнес
Справочники
Советы







Материалы книги получены с http://www.itlibitum.ru/

Управление памятью с применением ведущих указателей

Похоже, я соврал; в этой главе нам все же придется вернуться к умным указателям. Управление памятью под руководством клиента можно усовершенствовать, инкапсулируя различные стратегии в умных ведущих указателях. Расширение архитектуры с локальными пулами демонстрирует основную идею, которая может быть приспособлена практически для любой схемы с управлением на уровне объектов.

Специализированные ведущие указатели

Простейшая стратегия заключается в создании специализированного класса ведущего указателя или шаблона, который знает о локальном пуле и использует глобальную перегрузку оператора new.

struct Pool { ... }; // Как и раньше

void* operator new(Pool* p); // Выделение из пула

template <class Type>

class PoolMP {

private:

Type* pointee;

PoolMP(const PoolMP<Type>&) {} // Копирование не разрешено...

PoolMP<Type>& operator=(const PoolMP<Type>&)

{ return *this; } // ...и присваивание тоже

public:

PoolMP(Pool* p) : pointee(new(p) Type) {}

~PoolMP() { pointee->~Type(); }

Type* operator->() const { return pointee; }

};

При желании клиент может использовать PoolMP для выделения и освобождения памяти в локальном пуле. Деструктор ведущего указателя вызывает деструктор указываемого объекта, но не освобождает память. Поскольку ведущий указатель не следит за исходным пулом, копирование и присваивание поддерживать не удастся, так как ведущий указатель понятия не имеет, в каком пуле создавать новые копии. Если не считать этих недостатков, перед нами фактически простейший указатель, не отягощенный никакими издержками.

На это можно возразить, что копирование и присваивание все же следует поддерживать, но с

использование операторов new и delete по умолчанию. В этом случае конструктор копий и оператор = работают так же, как и для обычного ведущего указателя.

Обратные указатели на пул

Чтобы поддерживать копирование и присваивание в пуле, можно запоминать адрес пула.

template <class Type>

class PoolMP {

private:

Type* pointee;

Pool* pool;

public:

PoolMP(Pool* p) : pointee(new(p) Type), pool(p) {}

~PoolMP() { pointee->Type::~Type(); }

PoolMP(const PoolMP<Type>& pmp) : pointee(new(pool) Type(*pointee)) {}

PoolMP<Type>& operator=(const PoolMP<Type>& pmp)

{

if (this == &pmp) return *this;

delete pointee;

pointee = new(pool) Type(*pointee);

return *this;

}

Type* operator->() const { return pointee; }

};

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

Сосуществование с обычными ведущими указателями

Предложенное решение отнюдь не идеально. Интерфейс PoolMP открывает многое из того, о чем следовало бы знать только классам. Более того, если вам захочется совместно работать с объектами из пула и объектами, размещенными другим способом (например, с помощью стандартного механизма), начинаются настоящие трудности. Ценой добавления v-таблицы мы сможем значительно лучше инкапсулировать отличия в стратегиях управления памятью.

template <class Type>

class MP {

protected:

MP(const MP<Type>&) {} // Копирование не разрешено

MP<Type>& operator=(const MP<Type>&)

{ return *this; } // Присваивание - тоже

MP() {} // Используется только производными классами

public:

virtual ~MP() {} // Освобождение выполняется производными классами

virtual Type* operator->() const = 0;

};

template <class Type>

class DefaultMP : public MP<Type> {

private:

Type* pointee;

public:

DefaultMP() : pointee(new Type) {}

DefaultMP(const DefaultMP<Type>& dmp)

: pointee(new Type(*dmp.pointee)) {}

virtual ~DefaultMP() { delete pointee; }

DefaultMP<Type>& operator=(const DefaultMP<Type>& dmp)

{

if (this == &dmp) return *this;

delete pointee;

pointee = new Type(*dmp.pointee);

return *this;

}

virtual Type* operator->() const { return pointee; }

};

template <class Type>

class LocalPoolMP : public MP<Type> {

private:

Type* pointee;

Pool* pool;

public:

LocalPoolMP(Pool* p)

: pointee(new(p) Type), pool(p) []

LocalPoolMP(const LocalPoolMP<Type>& lpmp)

: pointee(new(lpmp.pool) Type(*lpmp.pointee)), pool(lpmp.pool) {}

virtual ~LocalPoolMP() { pointee->Type::~Type(); }

LocalPoolMP<Type>& operator=(const LocalPoolMP<Type>& lpmp)

{

if (this == &lpmp) return *this;

pointee->Type::~Type();

pointee = new(pool) Type(*lpmp.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(Pool*); // Использует пул

virtual ~Foo() {}

// Далее следуют чисто виртуальные функции

};

// В файле foo.cpp

class PoolFoo : public Foo {

private:

Foo* foo;

Pool* pool;

public:

PoolFoo(Foo* f, Pool* p) : foo(f), pool(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(Pool* p)

{

return new PoolFoo(new(p) ConcreteFoo, p);

}

Такой вариант намного «чище» для клиента. Единственное место, в котором клиентский код должен знать что-то о пулах, - создание объекта функцией make(Pool*). Остальные пользователи полученного невидимого указателя понятия не имеют, находится их рабочий объект в пуле или нет.

Стековые оболочки

Чтобы добиться максимальной инкапсуляции, следует внести в описаннуюархитектуру следующие изменения:

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

2.   Предоставить функцию static Foo::makePool(). Функция make(Pool*) будет работать и

для других разновидностей Pool, но makePool() позволяет Foo выбрать производящую

функцию Pool, оптимальную для хранения Foo (например, с передачей размера экземпляра).

3.   Переработать старый шаблон MP из предыдущих глав (с операторной функцией operator Type*()), чтобы при выходе из пула и указателей за пределы области действия все

необходимое автоматически уничтожалось.

Ниже показан примерный вид полученного интерфейса, с фрагментом клиентского кода и без виртуального оператора =.

// В файле foo.h

// Подключить объявление чисто абстрактного базового класса

#include "pool.h"

class Foo {

private:

Foo(const Foo&) {}

Foo& operator=(const Foo&) { return *this; }

public:

static Pool* makePool(); // Создать пул, оптимизированный для Foo

static Foo* make(); // Не использует пул

static Foo* make(Pool*); // Использует пул

// И т.д.

};

// Клиентский код

void g(Foo*);

void f()

{

MP<Pool> pool(Foo::makePool());

MP<Foo> foo(Foo::make(pool));

foo->MemberOfFoo(); // Использует операторную функцию operator->()

g(foo); // Использует операторную функцию operator Type*()

// Выход из области действия - удаляется сначала foo, затем pool

}


Назад    Содержание    Далее    

Home  Создание сайтов  Учебник по записи CD  Справочник Web дизайнера Самоучитель IE PHP и MySQL Компьютерные сети С++ E-mail me

Copyright 2007. Климов Александр. All Right Reserved.
Hosted by uCoz