Программирование >>  Разработка устойчивых систем 

1 ... 102 103 104 [ 105 ] 106 107 108 ... 196


Стоит напомнить, что конечный итератор ссылается не на последний элемент, а на позицию за последним элементом. - Примеч. перев.

Ситуация скоро изменится, так как следующая версия стандарта должна специфицировать дополнительные т1пп>1 умных указателей. Если вы захотите познакомиться с ними заранее, найдите классы умных указателей на сайте www.boost.org.

вольного доступа и т. д.), все они обладают одинаковым базовым интерфейсом. Итератор можно перевести к следующему элементу оператором ++, разыменовать его для получения объекта, на который итератор указывает в данный момент, и проверить, не вышел ли он за границу интервала. Именно эти операции выполняются с итераторами в 90 % случаев. После создания контейнер заполняется указателями на различные подклассы Shape. Помните, что при включении указателей на Circle, Square или Rectangle в контейнер указателей на Shape происходит повышающее преобразование, так как контейнер ничего не знает об этих специализированных типах, а работает лишь с Shape*. Как только указатель включается в контейнер, он теряет свою индивидуальность и становится анонимным указателем Shape*. Происходит именно то, что нужно: мы смешиваем разные типы указателей и предоставляем механизму полиморфного вызова разбираться, что к чему.

В первом цикле for мы создаем итератор и устанавливаем его в начало интервала, вызывая функцию begin() класса контейнера. Во всех контейнерных классах определены функции begin() и end(), которые возвращают соответственно начальный и конечный итераторы. Чтобы узнать, не завершен ли перебор элементов контейнера, следует проверить, не равен ли итератор конечному итератору endl(). Для этого нельзя использовать операторы < и <=; работают только проверки !- и --, поэтому на практике часто встречаются циклы вида

fordter 1 = shapes.beginO; i !- shapes.endO: i++)

Это означает перебрать все элементы контейнера от начала до конца .

Как получить значение элемента, на который ссылается итератор? Конечно, разыменовать его перегруженным оператором *. Полученное значение соответствует типу элементов контейнера. В нашем примере контейнер содержит указатели Shape*, поэтому *i вернет указатель Shape*. Функция класса Shape по указателю вызывается оператором ->; значит, следующая строка вызывает функцию draw() для указателя Shape*, на который в данный момент ссылается итератор:

(*i)->draw();

Круглые скобки в левой части выглядят некрасиво, но они нужны для получения желаемого приоритета операторов.

При уничтожении и в других ситуациях, связанных с исключением указателей из контейнера, контейнеры STL не вызывают оператор delete для содержащихся в них указателей. Допустим, вы создали объект в куче оператором new и сохранили указатель на него в контейнере. Контейнер не знает, хранится этот указатель в другом контейнере или нет; он не знает даже того, относится или нет этот указатель к памяти в куче. Как всегда, программист несет всю ответственность за освобождение памяти, выделенной из кучи. В последних строках программы происходит зачистка: мы перебираем все указатели в контейнере и вызываем для каждого из них оператор delete. Учтите, что указатель auto ptr не может использоваться для этой цели, поэтому подходящие умные указатели придется искать за пределами стандартной библиотеки С++.



Вы можете изменить тип контейнера, используемого программой. Например, чтобы перейти от вектора к списку, достаточно изменения двух строк. Вместо включения заголовка <vector> включается заголовок <list>, а первое определение типа приводится к виду

typedef std::list<Shape*> Container:

Весь остальной код остается без изменений. Такая гибкость стала возможной благодаря интерфейсу, установленному не механизмом наследования (в STL наследование почти не используется), а правилами, которыми руководствовались разработчики STL специально для того, чтобы сделать возможной подобную замену. Теперь с вектора молено легко переключиться на список или любой другой контейнер с идентичным интерфейсом (как синтаксически, так и семантически) и посмотреть, какой вариант лучше подходит для ваших целей.

Хранение строк в контейнере

в предыдущем примере функция main() должна была завершаться перебором всего списка и вызовом оператора delete для указателей на Shape:

fordter j = Shapes.beginO: j != shapes.end(): delete *j:

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

Так мы приходим к тому, что на первый взгляд кажется просчетом разработчиков STL: ни в одном контейнере STL не предусмотрена возможность авто.матиче-ского вызова оператора delete для содержащихся в контейнере указателей, поэтому оператор delete приходится вызывать вручную. Неужели разработчики решили, что контейнеры указателей не представляют интереса для программиста? Конечно, нет.

Автоматический вызов оператора delete для указателя создает проблемы из-за возможности множественной принадлежности элементов. Если контейнер содержит указатель на объект, вполне возможно, что этот указатель также хранится в другом контейнере. Указатель на объект Aluminium, хранящийся в списке указателей на Trash, также может храниться в списке указателей на Aluminium. Какой из двух списков в этом случае должен отвечать за уничтожение объекта, или, другими словами, какому из списков принадлежит этот объект?

Проблема полностью исчезает, если вместо указателей в списке хранятся объекты. В этом случае вполне очевидно, что при уничтожении списка также должны уничтожаться все хранящиеся в нем объекты. Здесь STL проявляет себя с лучшей стороны; чтобы убедиться в этом, достаточно создать контейнер с объектами типа string. В следующем примере входные строки сохраняются в виде строковых объектов в векторе vector<string>:

: С07:StringVector.срр Вектор строк #include <fstream> #include <iostream> #include <iterator> #include <sstream> #include <string>



#i nclude <vector> #1nclude ../require.h using namespace std:

int main(int argc. char* argv[]) { char* fname = StringVector.cpp : if(argc > 1) fname = argv[l]: ifstream in(fname): assure(in, fname): vector<string> strings: string line:

while(getline(in. line))

strings.push back(line); Операции со строками... int i = 1:

vector<string>::iterator w: for(w = strings.beginO:

w != strings.end(): w++) {

ostringstream ss:

ss i++:

*w = ss.strO + : + *w:

Вывод содержимого контейнера: copy(strings.begin(). strings.endO.

ostream iterator<string>(cout. \n )); Поскольку строки не являются указателями. объекты string уничтожаются автоматически! } /:-

Программа сначала создает вектор vector<string> с именем strings, читает строки из файла в объекты string и сохраняет их в векторе:

while(getline(in. line)) strings.push back(line):

В данном примере строки, прочитанные из файла, нумеруются. Класс stringstream обеспечивает удобное преобразование int в строку, представляющую данное число.

Перегрузка оператора + упрощает сборку объектов string из отдельных компонентов. Существует и другая удобная возможность: разыменование итератора w дает строку, которая может использоваться как в правой, так и в левой части команды присваивания:

*w = ss.strO + : + *w:

Присваивание элементам контейнера через разыменованный итератор выглядит несколько неожиданно, но является следствием тщательно продуманной архитектуры STL.

Так как вектор vector<string> содержит объекты, а не указатели, стоит обратить внимание на два обстоятельства. Во-первых, как объяснялось ранее, вам не придется явно уничтожать объекты string. Даже если сохранить адреса объектов string в виде указателей в других контейнерах, понятно, что контейнер с объектами string является главным , и право владения объектами принадлежит именно ему.

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



1 ... 102 103 104 [ 105 ] 106 107 108 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика