Шаг 9.
Библиотека шаблонов классов Borland.
Развитие "сознания" класса. Как использовать принадлежность объектов

    На этом шаге мы рассмотрим использование принадлежности об'ектов.

    После задания статуса владения объектами для косвенного контейнера можно вызвать функции-члены для удаления одного или нескольких объектов из контейнера и, возможно, удаления при этом самих объектов. В листинге 1 демонстрируются несколько основных приемов.

    Листинг 1. OWNER.CPP (демонстрация принадлежности объектов)

#include <iostream.h> 
#include <cstring.h>
#include <classlib\arrays.h>

// Определить алиасы имен типов
typedef TIArrayAsVector TContainer;
typedef TIArrayAsVectorIterator TIterator;

// Прототип функции отображения содержимого контейнера
void ShowMe(const char *msg, TContainer &cr);

int main()
{
TContainer *cp;             // Указатель на контейнер
string s("Локальный"); // Строковый объект

// Создать контейнер и отобразить его права владения
cp = new TContainer(10, 0, 10);
cout << endl;
cout << "Масссив владеет объектами: ";
if (cp->OwnsElements())
	 cout << "TRUE" << endl;
 else
	 cout << "FALSE" << endl;

// Запомнить несколько строковых объектов в контейнере
cp->Add(new string("Красный"));
cp->Add(new string("Голубой"));
cp->Add(new string("Зеленый"));
cp->Add(new string("Белый"));
cp->Add(&s);     // !!'Опасность!!! Смотри комментарий
ShowMe("Исходный массив:", *cp);

// Удалить объект из контейнера и уничтожить по индексу
cp->Detach(2, TShouldDelete::Delete);
ShowMe("После вызова: Detach(2, Delete)", *cp);

// Удалить объект из контейнера, НЕ удаляя его при этом
cp->Detach(&s, TShouldDelete::NoDelete);
ShowMe("После вызова: Detach(&s, NoDelete)", *cp);

// Удалить последний объект и, возможно, уничтожить его
int n = cp->GetItemsInContainer();
cp->Detach(n - 1, TShouldDelete::DefDelete);
ShowMe("После вызова: Detacla(n - 1, DefDelete)", *cp);

// Удалить все объекты из контейнера, и, возможно, уничтожить их
cp->Flush();
ShowMe("Вызов: Flush()",*cp);

// Удалить контейнер.  НЕ удалять объекты???
delete cp;
return 0;
}


// Отображает содержимое контейнера cr,  переданного по ссылке
void ShowMe(const char *msg, TContainer &cr)
{
TIterator iterator(cr);                      // Создать итератор для контейнера
string *sp;	                               // Указатель на объекты контейнера

cout << endl << msg << endl;                 // Отобразить сообщение
cout << "Элементов в контейнере = ";         // Отобразить сообщение
cout << cr.GetItemsInContainer() << endl;    // Отобразить число элементов
while ((sp = iterator++)!= 0)                // Цикл по итератору
  cout << " " << *sp;                        // Отобразить элементы
cout << endl;	                               // Перевести строку
}

Текст этого приложения можно взять здесь.

   Результат работы программы:

Масссив владеет объектами: TRUE

Исходный массив: 
Элементов в контейнере = 5
  Красный Голубой Зеленый Белый Локальный

После вызова: Detach(2, Delete)
Элементов в контейнере = 4
  Красный Голубой Белый Локальный

После вызова: Detach(&s, NoDelete)
Элементов в контейнере = 3
  Красный Голубой Белый 

После вызова: Detach(n-1, DefDelete)
Элементов в контейнере = 2
  Красный Голубой 

Вызова: Fluch()
Элементов в контейнере = 0

    Для сокращения чрезмерно длинных объявлений в программе определяются два алиаса типов. Тип TContainer определяется как косвенный массив строк, реализованный с помощью вектора. Тип TIterator - итератор для этого контейнера. В программе объявляется указатель на тип TContainer, и с помощью оператора new создается объект контейнера:

TContainer *cp; 
cp = new TContainer(10, 0, 10);

    После отображения сообщения о том, владеет ли контейнер, адресуемый указателем ср, своими объектами (по умолчанию владеет), в контейнере запоминается несколько строковых объектов с помощью следующих операторов:

cp->Add(new string("Красный")); 

    Обратите внимание, что так же передается адрес временного строкового объекта s, размещающегося в системном стеке:

cp->Add(&s);     // !!!

    Это очень опасный трюк, которым на практике лучше не пользоваться. Однако, если вам необходимо добавить локальные объекты по адресу в косвенный контейнер, вы можете сделать это безопасно, если последуете совету, приведенному далее в этом шаге. Что особенно важно, вы не должны позволять контейнеру уничтожать этот объект. Такая операция может связать часть стекового пространства с резервом свободной памяти кучи, что неминуемо приведет к порче стека и кучи при выделении памяти в области верхушки стека, хранящей адреса возврата функций.

    Для удаления объекта из контейнера в программе выполняется следующий оператор:

cp->Detach(2, TShouldDelete::Delete);

    Аргумент 2 означает, что третий объект должен быть удален из контейнера. Другие объекты сдвигаются вверх для уплотнения освобожденного объектом места. Аргумент TShouldDelete::Delete задает, что этот объект должен быть уничтожен независимо от статуса владения.

    Можно также задать, чтобы объект не уничтожался вне зависимости от статуса владения. Например, необходимо удалить локальный строковый объект из контейнера таким образом:

cp->Detach(&s, TShouldDelete::NoDelete); 

    Следует выполнить аналогичные действия для каждого локального объекта в косвенном контейнере до вызова любой другой функции, которая может привести к уничтожению такого объекта. Например, если в программе сначала не выполняется удаление из контейнера локального объекта s, следующий оператор, вероятнее всего, испортит кучу:

cp->Flush();

    При вызове функции Flush() удаляются все объекты, которыми владеет контейнер. Для предотвращения сброса вашего кода в "канализацию" в случае, если контейнер адресует локальные объекты, следует или отменить владение объектами, или заблаговременно удалить такие объекты из контейнера.

    Можно сделать так, чтобы объекты удалялись при условии, что контейнер владеет ими. Например, в программе можно удалить последний объект, индексированный целым n следующим образом:

cp->Detach(n-1, TShouldDelete::DefDelete);

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

    Наконец, в примере программы уничтожается контейнер, память для которого выделялась в свое время с помощью оператора new:

delete cp;

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

  1. Вызвать функцию Flush() перед удалением контейнера.
  2. Вывести новый контейнерный класс и написать к нему деструктор, который вызывает функцию Flush().

    Первое решение, с успехом примененное в примере программы, самое простое, но если вы забудете вызвать функцию Flush(), в вашей программе может образоваться неприятная утечка памяти. Второе решение гарантирует уничтожение динамического контейнера с предварительным вызовом функции Flush(). Для реализации последнего объявите новый шаблон класса в модуле OWNER.CPP:

template <class T> class TIMyArrayAsVector :
public TIArrayAsVector <T> 
{ 
public:
  TIMyArrayAsVector(int upper,  int lower = 0,  int delta = 0) :
     TIArrayAsVector<T>(upper,   lower,  delta) { }
  ~TIMyArrayAsVector() { Flush(); } 
}

    В шаблоне класса TIMyArrayAsVector вызывается конструктор его предка. Деструктор вызывает функцию Flush() с аргументом по умолчанию DefDelete. Контейнер, созданный из этого класса, автоматически уничтожает принадлежащие ему объекты, когда контейнер уничтожается или выходит из области видимости.

    Если вы внесли предыдущие изменения в модульOWNER.CPP, перепишите алиас typedef для TContainer так, как приведено ниже. Теперь в модифицированной программе будет использоваться новый производный класс.

typedef TIMyArrayAsVector<string> TContainer;

    Замечание. Ввод производного класса в программу часто требует модификации всех прежних употреблений класса-предка. Однако с помощью алиаса, подобного TContainer, можно модифицировать программу так, чтобы в ней, использовался новый класс, изменив всего лишь одно объявление typedef. Следует представлять себе алиасы typedef как константы типов данных. Ими можно пользоваться точно так же, как и другими константами, для упрощения поддержки программы.

    Мы закончили изложение этого материала. Надеемся, что он будет вам полезен.




Предыдущий шаг Содержание