|
Программирование >> Разработка устойчивых систем
Слнглет, один из хорошо известных эталонов проектирования, подробно рассматривается в главе 10. NoisyReport & operator=(NoisyReport &); Запрещено NoisyReport (const NoisyReport&); Запрещено public: ~NoisyReport() { cout \n------.........----\n Noisy creations: Noisy::create \nCopy-Constructions: Noisy::copycons \nAssignments: Noisy::assign \nDestructions: Noisy::destroy endl: #endif NOISY H /:- : C07:Noisy.cpp{0} #include Noisy.h long Noisy::create = 0. Noisy::assign = 0. Noisy;:copycons = 0. Noisy::destroy = 0: NoisyReport NoisyReport::nr: III:- Каждый объект Noisy обладает собственным идентификатором, а все операции создания, присваивания, конструирования копий и уничтожения отслеживаются в статических переменных. Идентификатор id инициализируется на основании счетчика create в конструкторе по умолчанию; копирующий конструктор и оператор присваивания берут свои значения id из копируемого/присваиваемого объекта. В операторной функции operator= присваивание производится уже инициализированному объекту, поэтому прежнее значение id выводится перед его заменой на id присваиваемого объекта. Для поддержки операций, автоматически выполняемых некоторыми контейнерами (таких, как сортировка и поиск), в Noisy необходимо определить операторы < и ==. Эти операторы просто сравнивают значения id. Оператор вывода в поток ostream написан по стандартному образцу и просто выводит значение id. Тип NoisyGen представляет объекты функции (на что указывает оператор ())для создания объектов Noisy. Объект NoisyReport реализован как Синглет, потому что при заверщении программы должен выводиться только один экземпляр отчета. Он имеет закрытый конструктор, предотвращающий создание дополнительных объектов NoisyReport, запрещает присваивание и конструирование копий, а также содержит один статический экземпляр NoisyReport с именем nr. Все исполняемые команды находятся в деструкторе, который вызывается в фазе вызова статических деструкторов при заверщении программы. Деструктор выводит статистику, накопленную в статических переменных Noisy. Следующая программа использует файл Noisy.h для демонстрации переполнения вектора: : C07:VectorOverflow.cpp {-Ьог} Демонстрация конструирования копий и уничтожения объектов при перераспределении памяти вектора #i nclude <cstdlib> #include <iostream> #i nclude <string> #i nclude <vector> linclude Noisy.h using namespace std: int main(int argc, char* argv[]) ( int size = 1000: if(argc >= 2) size = atoi(argv[l]): vector<Noisy> vn: Noisy n: for(int i = 0: i < size: i++) vn.push back(n): cout \n cleaning up endl: } /:- Вы можете использовать значение по умолчанию 1000 или передать собственный размер массива в командной строке. При запуске программы выводится информация об одном вызове конструктора по умолчанию (для п), затем о множестве вызовов копирующего конструктора, далее о вызовах деструкторов, новых вызовах копирующих конструкторов и т. д. Когда в линейном массиве, выделенном вектором, кончается свободная память, вектор должен выделить новый блок памяти большего размера (чтобы сохранить непрерывное размещение объектов, являющееся важным свойством вектора) и перенести туда все текущее содержимое. Но для этого необходимо сначала скопировать, а затем уничтожить старые объекты. Как нетрудно представить, при большом количестве сложных объектов эти затраты быстро становятся неприемлемыми. У проблемы есть два решения. В первом (оптимальном) случае необходимо заранее знать, сколько объектов будет создано. Тогда вы заранее резервируете необходимую память функцией reserve(); все лишнее копирование и уничтожение ликвидируется, и все операции работают очень быстро (особенно произвольный доступ к объектам, реализуемый оператором [ ]). Обратите внимание на отличие функции reserveO от конструктора vector с целочисленным первым аргументом; последний инициализирует заданное количество элементов, используя конструктор по умолчанию для типа элементов. Однако в общем случае количество объектов заранее неизвестно. Если перераспределения памяти существенно замедляют работу с вектором, попробуйте перейти на другой последовательный контейнер. Можно воспользоваться списком, но как вскоре будет показано, дек обеспечивает быструю вставку с любого конца последовательности и не требует копирования/уничтожения объектов при перераспределении памяти. Дек также поддерживает индексацию элементов оператором [ ], но по скорости она уступает оператору [ ] вектора. Следовательно, если вы создаете все объекты в одной части программы, а затем организуете произвольный доступ к ним из другой части, в принципе можно попробовать заполнить дек, а затем создать вектор на базе дека и использовать его для быстрой индексации. Впрочем, не стоит прибегать к этому приему в повседневной работе - просто помните о такой возможности (преждевременные оптимизации вообще нежелательны). Однако у перераспределения памяти есть и другие более серьезные последствия. Поскольку вектор хранит свои объекты в удобном, компактном массиве, используемые им итераторы могут представлять собой простые указатели. И это хорошо - именно эта особенность обеспечивает векторам самую быструю выборку и обработку данных среди последовательных контейнеров. Но давайте посмотрим, что произойдет при добавлении одного дополнительного объекта, после которого вектор выделяет новую память и перемещает данные в другое место. Указатель итератора указывает в никуда ! : C07:VectorCoreDump.cpp Появление недействительных итераторов #1nclude <iterator> linclude <iostream> linclude <vector> using namespace std: int mainO { vector<int> vi(10. 0): ostream iterator<int> out(cout. ): vector<int>::iterator i = vi.beginO: *i = 47: copy(vi .begin(), vi.endO. out): cout endl: Выполняем принудительное перераспределение памяти (также можно было бы добавить нужное количество объектов): vi .resize(vi .capacityO + 1): Теперь i ссылается на недействительную память: *i = 48: Нарушение доступа сору (vi .beginO. vi.endO. out): vi[0] не изменяется } III:- Данный пример иллюстрирует концепцию недействительности итераторов. Некоторые операции приводят к изменениям в базовой структуре данных контейнера, поэтому итераторы, действительные до выполнения операции, могут оказаться недействительными после нее. Если ваша программа зависает по каким-то загадочным причинам, посмотрите, не используются ли старые итераторы после включения новых объектов в вектор. После добавления элементов следует получить новый итератор или производить выборку элементов оператором [ ]. А если связать этот факт с потенциальными затратами на добавление новых объектов в вектор, становится ясно, что самый безопасный вариант использования вектора - его однократное заполнение заранее известным количеством объектов и последующая работа с ним без добавления новых объектов. В частности, именно так применялись векторы в приводившихся ранее примерах. В стандартной библиотеке С++ документированы операции с контейнерами, в результате которых итераторы становятся недействительными. Стоит заметить, что выбор вектора в качестве основного контейнера для приводившихся примеров не всегда оптимален. В этом проявляется основополагающий принцип выбора контейнеров и структур данных вообще - оптимальный выбор зависит от того, как будет использоваться контейнер. До сих пор мы постоянно применяли вектор лишь из-за его сходства с массивом, благодаря чему он выглядит знакомо и легко осваивается. Но в дальнейшем при выборе контейнера также необходимо будет учитывать ряд других факторов. Вставка и удаление элементов Вектор работает наиболее эффективно, если выполняются два условия. 1. Программист заранее выделяет нужный объем памяти функцией reserve(), чтобы предотвратить дальнейшие перераспределения памяти. 2. Все операции добавления и удаления элементов выполняются только в конце вектора. Вообще говоря, вы можете вставлять и уничтожать элементы в середине вектора. Но следующая программа показывает, почему этого делать не стоит: : C07;VectorInsertAndErase.cpp {-Ьог} Уничтожение элемента вектора
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |