Документация
Знакомство с CntmLib
Общие сведения
Установка и использование
Основные пакеты
Вспомогательные пакеты
Лицензия
Описание классов
|
НАЗНАЧЕНИЕ ПОДСЧЕТА ССЫЛОК
Подсчет ссылок на объекты предназначен для автоматизации управления временем жизни оъектов. Суть управления временем жизни объектов с помощью подсчета ссылок заключается в следующем. Объект обладает таким свойством, как кол-во ссылок на него. После создания объекта кол-во ссылок на него устанавливается в 1, т.к. создатель получает ссылку на объект. Если создатель передает ссылку на созданный объект еще кому-то, то счетчик ссылок объекта следует увеличить на 1. Когда использование объекта завершено, то следует уменьшить кол-во ссылок на 1 и больше не использовать ссылку. Когда счетчик ссылок достигает 0, это означает, что пользователей данного объекта не осталось и он должен запустить процедуру своего уничтожения.
КЛАССЫ, РЕАЛИЗУЮЩИЕ ПОДСЧЕТ ССЫЛОК
Базовые классы подсчета ссылок
Для декларации объектов, которые подсчитывают ссылки на себя (такие объекты будем называть ссылочными), библиотека предоставляет интерфейс Cntm::IRefObject. Данный интерфейс определяет правила управления подсчетом ссылок со стороны объекта. Правила подсчета ссылок приведены в описании интерфейса Cntm::IRefObject. Библиотека предоставляет базовые классы, реализующие интерфейс Cntm::IRefObject: Cntm::RefBase для простых случаев и Cntm::RefBaseEx для более сложных. Класс, который должен обеспечивать подсчет ссылок должен наследовать Cntm::RefBase или Cntm::RefBaseEx.
Агрегация
Сложный объект может содержать встроенные объекты, время жизни которых совпадает с временем жизни сложного объекта, т.е. за создание и уничтожение которых отвечает сложный объект. В этом случае сложный объект называют агрегатом, а встроенные объекты - агрегируемыми объектами. В некоторых случаях требуется использовать ссылки на агрегированные объекты. Т.к. время жизни агрегируемого объекта определяется агрегатом, то при увеличении кол-ва ссылок на агрегируемый объект следует увеличивать ссылки на агрегат. Аналогичная переадресация должна выполняться и при уменьшении кол-ва ссылок. Т.о. агрегат будет существовать, пока есть ссылки на него или на его агрегированные объекты. Данная политика выпоняется классом Cntm::AggregatedBase, который реализует интерфейс Cntm::IRefObject. Если объект предназначен для агрегирования, то его класс должен наследовать Cntm::AggregatedBase. Класс Cntm::AggregatedBaseпросто перенаправляет вызовы методов увеличения и уменьшения ссылок агрегату, который передан ему при создании.
Ссылочные указатели
Управление увеличением и уменьшением счетчика ссылок со стороны пользователей объекта автоматизировано с помощью ссылочных указателей. Сылочные указатели представлены классом Cntm::RefPtr<T>. Это шаблонный класс, где T - класс, на объекты которого будет указывать ссылочный указатель. При чтении он ведет себя практически как обычный указатель T*, т.е. для него определены операторы ->, *, bool, ! и ==. Но при создании и записи этот класс выполняет действия по изменению кол-ва ссылок. Для данного класса существуют 2 вида конструкторов и операторов присваивания: принимающие указатель на объект - T* и принимающие ссылочный указатель. Конструкторы и операторы присваивания увеличивают кол-во ссылок на присваиваемый объект. Деструктор и операторы присваивания уменьшают кол-во ссылок на текущий объект.
Если при инстанциации шаблона Cntm::RefPtr<T> тип T не является классом, подсчитывающим ссылки, т.е. не является Cntm::IRefObject или его наследником,то Cntm::RefPtr ведет себя как обычный указатель, т.е. не выполняет ни каких действий по изменению кол-ва ссылок и, соответственно не управляет уничтожением объекта.
Обобщенные ссылочные указатели
Назначение обобщенных ссылочных указателей такое же, что и у обычных - управление подсчетом ссылок. Однако между ними имеется большое отличие - обобщенные указатели не являются шаблонами, т.е. не зависят от типа объекта, на который указывают. Через обобщенный указатель нельзя использовать для доступа к членам класса, т.к. он хранит указатель на void*. Обобщенный указатель следует использовать тогда, когда требуется управление подсчетом ссылок, не нужно обращаться к членам класса и желательно отказаться от шаблонов. При присвоении не ссылочного объекта (унаследованного не от Cntm::IRefObject) ни каких действий по изменению кол-ва ссылок не выполняется. Подробности в описании класса Cntm::GenericRefPtr.
В некоторых случаях требуется обеспечить потенциальную возможность управлять подсчетом ссылок. Это реализуется с помощью класса Cntm::GenericNoRefPtr, который не изменяет кол-во ссылок сам по себе, но м.б. присвоен обобщенному ссылочному указателю Cntm::GenericRefPtr.
Кроме обычных обобщенных указателей имеются обобщенные указатели на методы объектов Cntm::GenericMethodRefPtr и Cntm::GenericMethodNoRefPtr. Они соединяют обобщенные указатели на объект с адресом метода этого объекта. Их поведение полностью аналогично Cntm::GenericRefPtr и Cntm::GenericNoRefPtr соответственно.
ПРИМЕР ИСПОЛЬЗОВАНИЯ
Простейшее использование
Простейшее использование подсчета ссылок заключается в следующем. Нужно класс, объекты которого должны поддерживать подсчет ссылок, унаследовать от RefBase и определить деструктор, который будет вызван когда на объект не останется ссылок, и определение ссылочного указателя для данного класса typedef RefPtr Ptr - для удобства использования.
class ClassName: public RefBase
{
public:
typedef RefPtr<ClassName> Ptr;
int Result;
ClassName() { ... }
~ClassName() { ... }
void Exec() { ... }
};
Пример работы с классом выглядит так:
int main()
{
ClassName::Ptr ptr = new ClassName;
ptr->Exec();
return ptr->Result;
}
После завершения работы функции в деструкторе ptr будет уничтожен объект класса ClassName,т.к. на него не осталось ссылок.
Пример агрегации
class Aggregate1: public RefBase
{
public:
typedef RefPtr<Aggregate1> Ptr;
class Inner1: public AggregatedBase
{
public:
typedef RefPtr<Inner1> Ptr;
Inner1(Aggregate1 * Aggregate): AggregatedBase(Aggregate) {}
int Process() { /* ... */ }
};
Aggregate1(): RefBase(), inner(new Inner1(this)) {}
const Inner1::Ptr& Inner() const { return inner; }
private:
Inner1::Ptr inner;
};
Пример использования:
int main()
{
Aggregate1::Ptr ptr = new Aggregate1;
Aggregate1::Inner1::Ptr iptr = ptr->Inner();
ptr = NULL;
return iptr->Process();
}
Обобщенные указатели
void main()
{
TD1::Ptr pd1 = new TD1; // Создаем обычный ссылочный указатель.
GenericNoRefPtr gnp1 = pd1; // Инициализируем обобщенный нессылочный
указатель, кол-во ссылок не увеличивается.
GenericRefPtr gp1 = gnp1; // Присваиваем обобщенный нессылочный обобщенному
ссылочному, кол-во ссылок увеличивается.
} // Уничтожается gp1 - кол-во ссылок уменьшается,
// уничтожается pd1 - кол-во ссылок уменьшается.
ОСОБЕННОСТИ ИСПОЛЬЗОВАНИЯ
Удаляемое состояние объекта
При достижении счетчиком ссылок 0 объект переводится в т.н. удаляемое состояние, в котором он может находиться до своего физического удаления. Особенность этого состояния объекта в том, что при попытке создать новую ссылку на объект увеличение счетчика ссылок не происходит, а вместо указателя на объект возвращается NULL. Механизм подсчета ссылок похож на защелку, если счетчик ссылок достиг 0, то без принудительного вмешательства он уже никогда не будет увеличиваться.
В некоторых случаях (например, кольцевые ссылки, которые будут рассмотрены ниже) может использоваться обычный указатель на объект. Чтобы обратиться к объекту следует обычный указатель превратить в ссылочный, т.к. только он дает гарантию, что объект существует и находится в рабочем состоянии. Когда же объект находится в удаляемом состоянии, то преобразование из обычного указателя в ссылочный запрещено. Что касается обычного указателя,то его можно считать действительным только до момента выхода из деструктора класса, реализующего подсчет ссылок. При конструировании ссылочного указателя из обычного указателя или при установке нового значения из обычного указателя перед использованием следует проверять новый ссылочный указатель на NULL (когда ссылочному указателю присваивается значение указателя только что созданного объекта, то это правило можно не соблюдать). Эти особенности важны, когда помимо ссылочных применяются и обычные указатели, и в основном рассчитаны на многопоточное использование.
Кольцевые ссылки
В некоторых случаях нужно организовать взаимодействие 2 объектов, когда первый вызывает методы второго, а второй - первого. Первый объект должен иметь ссылку на второй, а второй на первый - это называется кольцевыми ссылками. Кольцо ссылок может образовываться и большим кол-вом объектов, когда 1 ссылается на 2, 2 на 3, а 3 на 1. При использовании ссылочных указателей кольцевые ссылки приведут к тому, что механизм подсчета ссылок перестает выполнять свои функции, т.к. ссылки на объекты кольца никогда не достугнут 0 и объкты не будут автоматически уничтожены.
Бороться с этим явлением можно 2 способами.
Первый способ - разрывать кольцо извне - выбрать один из объектов и подать ему команду на завершение, в которой он освободит все ссылки на другие объекты, что приведет к уничтожению всех объектов кольца, если на них не осталось других ссылок.
Второй способ - не создавать кольцо из ссылочных указателей, а использовать вместе ссылочные и обычные указатели. В этом случае перед использованием объекта следует создать временную ссылку, преобразовав обычный указатель в ссылочный, т.к. только ссылочный указатель гарантирует успешный доступ к объекту, управляемому подсчетом ссылок, а после использования уничтожить или сбросить ссылочный указатель для удаления этой временной ссылки.
Рассмотрим пример. Пусть имеется иерархическая структура объектов, где есть главный объект и подчиненные ему объекты. Предположим, что главный объект может существовать без подчиненных, а подчиненный без главного - нет. При использовании 1 способа главный объект имеет массив ссылочных указателей на подчиненные объекты, а каждый подчиненный объект имеет ссылочный указатель на главный. Тогда, чтобы произвести подготовку к уничтожению структуры, в определенный момент времени следует подать команду главному объекту, чтобы он уничтожил все ссылки на подчиненные объекты, что разорвет кольцо из главного и подчиненного объектов. При использовании 2 способа главный объект имеет массив обычных указателей, а каждый подчиненный объект имеет ссылочный указатель на главный. Ничего не мешает автоматическому уничтожению этой структуры, однако при обращении к подчиненным объектам главный должен создавать временные ссылочные указатели из обычных, а при уничтожении подчиненный должен сообщить о своем уничтожении главному.
Ссылочные указатели в конструкторе
Использование ссылочных указателей на конструируемый объект в конструкторе запрещено по двум причинам.
Во первых, если создать указатель и передать его на хранение другому объекту, а после этого произойдет исключение, не обработанное в самом конструкторе, то будет вызван деструктор, который уничтожит объект. Последствия этого могут быть весьма печальны, т.к. остался ссылочный указатель, указывающий на несуществующий объект. Когда этот указатель будет удаляться или ему будет присваиваться новое значение, то произойдет вызов метода несуществующего объекта, что приведет либо к краху программы либо к порче участка памяти.
Во вторых, если в конструкторе был создан ссылочный указатель на данный объект, а затем уничтожен, то это приведет к тому, что объект будет удален, так и не успев создаться, что тоже приведет к печальным последствиям.
СМ.
Описание интерфейса Cntm::IRefObject
Описание классов Cntm::RefBase, Cntm::RefBaseEx, Cntm::AggregatedBase
Описание класса Cntm::RefPtr
Описание классов Cntm::GenericRefPtr, Cntm::GenericNoRefPtr, Cntm::GenericMethodRefPtr, Cntm::GenericMethodNoRefPtr
Пример использования RefPtrExp.cpp
|