<?xml version="1.0" encoding="utf-8" ?>
<FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0" xmlns:l="http://www.w3.org/1999/xlink">
<description>
<title-info>
<genre match="100">comp_programming</genre>
<author>
<first-name>Скотт</first-name>
<middle-name></middle-name>
<last-name>Мейерс</last-name>
</author>
<book-title>Эффективное использование STL</book-title>
<annotation>
<p>В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.</p>
<p>Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.</p>
</annotation>
<keywords>Programming STL C++</keywords>
<date>2001</date>
<coverpage>
<image l:href="#img_0.jpeg"/>
</coverpage>
<lang>ru</lang>
<translator>
<first-name>E.</first-name>
<middle-name></middle-name>
<last-name>Матвеев</last-name>
</translator>
<sequence name="Библиотека программиста" number="0"/>
</title-info>
<document-info>
<author>
<first-name></first-name>
<middle-name></middle-name>
<last-name></last-name>
<nickname>Ulenspiegel</nickname>
</author>
<program-used>ExportToFB21</program-used>
<date value="2010-03-16">16.03.2010</date>
<id>OOoFBTools-2010-3-16-14-44-59-1482</id>
<version>1.0</version>
</document-info>
<publish-info>
<book-name>Эффективное использование STL</book-name>
<publisher>Питер</publisher>
<city>СПб.</city>
<year>2002</year>
<isbn>ISBN 5-94723-382-7</isbn>
<sequence name="Библиотека программиста" number="  0"/>
</publish-info>
<custom-info info-type="ББК">32.973-018</custom-info>
<custom-info info-type="УДК">681.3.06</custom-info>
</description>
<body>
<title>
<p>Эффективное использование STL</p>
</title>
<section>
<title>
<p>Предисловие</p>
</title>
<epigraph>
<p>«...На нем не было ленточки! Не было ярлыка! Не было коробки и не было мешка!»</p>
<text-author>Доктор Зюсс, «Как Гринч украл Рождество»</text-author>
</epigraph>
<p>Я впервые написал о STL (Standard Template Library) в 1995 году. Моя книга «More Effective С++» завершалась кратким обзором библиотеки. Но этого оказалось недостаточно, и вскоре я начал получать сообщения с вопросом, когда будет написана книга «Effective STL».</p>
<p>Несколько лет я сопротивлялся этой идее. Сначала я не обладал достаточным опытом программирования STL и не считал возможным давать советы. Но время шло, и на смену этой проблеме пришли другие. Бесспорно, появление библиотеки означало прорыв в области эффективной масштабируемой архитектуры, но в области <emphasis>использования </emphasis>STL возникали чисто практические проблемы, на которые было невозможно закрывать глаза. Адаптация любых программ STL, за исключением самых простейших, была сопряжена с множеством проблем, что объяснялось не только различиями в реализациях, но и разным уровнем поддержки шаблонов компиляторов. Учебники по STL были редкостью, поэтому постижение «дао программирования в STL» оказывалось задачей непростой. А как только программист справлялся с этой трудностью, возникала другая — поиск достаточно полной и точной справочной документации. Даже самая мелкая ошибка при использовании STL сопровождалась лавиной диагностических сообщений компилятора, длина которых достигала нескольких тысяч символов, причем в большинстве случаев речь шла о классах, функциях и шаблонах, не упоминавшихся в программе. При всем уважении к STL и разработчикам этой библиотеки я не решался рекомендовать ее программистам среднего звена. Я не был уверен в том, что STL <emphasis>можно </emphasis>использовать эффективно.</p>
<p>Затем я заметил нечто удивительное. Несмотря на все проблемы с переносом и скверное качество документации, несмотря на сообщения компилятора, напоминавшие бессмысленное нагромождение символов, многие из моих клиентов все равно работали с STL. Более того, они не просто экспериментировали с библиотекой, а использовали ее в коммерческих версиях своих программ! Для меня это</p>
<p>было откровением. Я знал, что программы, использующие STL, отличались элегантной архитектурой, но любая библиотека, ради которой программист добровольно обрекал себя на трудности с переносом, на скверную документацию и невразумительные сообщения об ошибках, должна была обладать чем-то большим, чем хорошая архитектура. Все больше профессиональных программистов полагало, что даже плохая реализация STL лучше, чем ее полное отсутствие.</p>
<p>Более того, я знал, что ситуация с STL будет улучшаться. Библиотеки и компиляторы будут постепенно приближаться к требованиям Стандарта (так оно и было), появится качественная документация (см. список литературы на с. 203), а диагностика компилятора станет более вразумительной (в этой области ситуация оставляет желать лучшего, но рекомендации совета 49 помогут вам с расшифровкой сообщений). В общем, я решил внести свою лепту в движение STL. Так появилась эта книга — 50 практических советов по использованию STL в С++.</p>
<p>Сначала я намеревался написать книгу за вторую половину 1999 г. и даже набросал ее примерную структуру. Но потом планы изменились, я приостановил работу над книгой и разработал вводный курс по STL, который преподавался нескольким группам программистов. Примерно через год я вернулся к книге и значительно расширил материал на основании опыта, полученного за время преподавания. В книге я постарался осветить практические аспекты программирования в STL, особенно важные для профессиональных программистов.</p>
<p><emphasis>Скотт Дуглас Мейерс Стаффорд, Орегон Апрель 2001 г.</emphasis></p>
</section>
<section>
<title>
<p>Благодарности</p>
</title>
<section>
<p>За годы, которые потребовались на то, чтобы разобраться в STL, разработать учебный курс и написать эту книгу, я получил неоценимую помощь и поддержку от множества людей. Хочу особо отметить Марка Роджерса (Mark Rodgers), великодушно предложившего просматривать материалы учебного курса по мере их написания. От него я узнал об STL больше, чем от кого-либо другого. Марк также выполнял функции технического редактора этой книги, а его замечания и дополнения помогли улучшить практически весь материал.</p>
<p>Другим выдающимся источником информации были конференции Usenet; посвященные языку С++, особенно <strong>comp.lang.c++.moderated («clcm»), comp.std.c++ </strong>и <strong><a l:href="http://microsoft.public.vc.stl/">microsoft.public.vc.stl</a></strong>. Свыше десяти лет участники этих и других конференций отвечали на мои вопросы и ставили задачи, над которыми мне приходилось думать. Я глубоко благодарен сообществу Usenet за помощь в работе над этой книгой и моими предыдущими публикациями по С++.</p>
<p>Мое понимание STL формировалось под влиянием ряда публикаций, самые важные из которых перечислены в конце книги. Особенно много полезного я почерпнул из труда Джосаттиса «The С++ Standard Library» [3].</p>
<p>Идеи и наблюдения, из которых состоит эта книга, в основном принадлежат другим авторам, хотя в ней есть и ряд моих собственных открытий. Я постарался по возможности отследить источники, из которых был почерпнут материал, но эта задача обречена на провал, поскольку информация собиралась из множества источников в течение долгого периода времени. Приведенный ниже список далеко не полон, но ничего лучше предложить не могу. Учтите, что в этом списке перечислены источники, из которых я узнавал о тех или иных идеях и приемах, а не их первооткрыватели.</p>
<p>В совете 1 замечание о том, что узловые контейнеры обеспечивают лучшую поддержку транзакционной семантики, позаимствовано из раздела 5.11.2 «The С++ Standard Library» [3]. Пример использования <code>typedef</code> при изменении типа распределителя памяти из совета 2 был предложен Марком Роджерсом. Совет 5 вдохновлен статьeй Ривса (Reeves) «STL Gotchas» [17]. В основу совета 8 заложен совет 37 книги Саттера «Exceptional С++» [8], а Кевлин Хенни (Kevlin Henney) предоставил важную информацию о проблемах, возникающих при использовании контейнеров <code>auto_ptr</code><emphasis>. </emphasis>В конференциях Usenet Мэтт Остерн (Matt Austem) предоставил примеры использования распределителей памяти, включенные мной в совет 11. Совет 12 основан на материалах сайта SGI STL [21], посвященных потоковой безопасности. Информация о подсчете ссылок в многопоточной среде из совета 13 почерпнута из статьи Саттера [20]. Идея совета 15 была подсказана статьей Ривса «Using Standard string in the Real World, Part 2» [18]. Методика непосредственной записи данных в <code>vecto<emphasis>r</emphasis></code><emphasis>, </emphasis>продемонстрированная в совете 16, была предложена Марком Роджерсом. В совет 17 была включена информация из Usenet, авторы — Симел Наран (Siemel Naran) и Карл Баррон (Carl Barron). Совет 18 был позаимствован из статьи Саттера «When Is a Container Not a Container?» [12]. Для совета 20 Марк Роджерс предложил идею преобразования указателя в объект посредством разыменования, а Скотт Левандовски (Scott Lewandowski) разработал представленную версию DereferenceLess. Совет 21 основан на сообщении Дуга Харрисона (Doug Harrison) в конференцию <a l:href="http://microsoft.public.vc.stl/">microsoft.public.vc.stl</a>, но решение о том, чтобы ограничить рамки этого совета проблемой равенства, принял я сам. Совет 22 основан на статье Саттера «Standard Library News: sets and maps» [13]. Совет 23 был подсказан статьей Остерна «Why You Shouldn't Use set — and What to Use Instead» [15]; Дэвид Смоллберг (David Smallberg) усовершенствовал мою реализацию DataCompare. Описание хэшированных контейнеров Dinkumware основано на статье Плаугера (Plauger) «Hash Tables» [16]. Марк Роджерс не согласен с выводами совета 26, но первоначально этот совет был подсказан его замечанием относительно того, что некоторые функции контейнеров принимают только аргументы типа iterator<emphasis>. </emphasis>Работа над советом 29 вдохновлялась дискуссиями в Usenet с участием Мэтта Остерна и Джеймса Канце (James Kanze); на меня также повлияла статья Клауса Крефта (Klaus Kreft) и Анжелики Лангер (Angelika Langer) «А Sophisticated Implementation of User-Defined Inserters and Extractors» [25]. Совет 30 основан на материалак раздела 5.4.2 книги Джосаттиса «The С++ Standard Library» [3]. В совете 31 Марко Далла Гасперина (Marco Dalla Gasperina) предложил пример использования nth_element для вычисления медианы, а использование этого алгоритма для поиска процентилей взято прямо из раздела 18.7.1 книги Страуструпа (Stroustrup) «The С++ Programming Language*. Совет 32 был вдохновлен материалами раздела 5.6.1 книги Джосаттиса «The С++ Standard Library*. Совет 35 появился под влиянием статьи Остерна «How to Do Case-Insensitive String Comparison» [11], а сообщения Джеймса Канце и Джона Потте-ра (John Potter) помогли мне лучше разобраться в сути происходящего. Реализация copy_if, приведенная в совете 36, позаимствована из раздела 18.6.1 книги Страуструпа «The С++ Programming Language» [7]. В основу совета 39 заложены публикации Джосаттиса, который впервые упомянул о «предикатах с состоянием» в своей книге «The С++ Standard Library» [3] и в статье «Predicates vs. Function Objects» [14]. В своей книге я использую его пример и рекомевдую предложенное им решение, хотя термин «чистая функция» принадлежит мне. В совете 41 Мeтт Остерн подтвердил мои подозрения относительно происхождения имен mem_fun и mem_fun_ref. Совет 42 берет свое начало из лекции, прочитанной мне Марком Роджерсом, когда я нарушил рекомендацию этого совета. Марку Роджерсу также принадлежит приведенное в совете 44 замечание о том, что при внешнем поиске в контейнерах map и multimap анализируются оба компонента пары, тогда как при поиске функциями контейнера учитывается только первый компонент (ключ). В совете 45 использована информация от разных участников clem, среди которых Джон Поттер, Марсин Касперски (Marcin Kasperski), Pete Becker (Пит Бекер), Деннис Йель (Dennis Yelle) и Дэвид Абрахаме (David Abrahams). Дэвид Смолл-берг подсказал мне идею применения equal_range для поиска на базе эквивалентности и подсчета результатов в сортированных последовательных контейнерах. Андрей Александреску (Andrei Alexandrescu) помог разобраться в условиях возникновения проблемы «ссылки на ссылку», упоминаемой в совете 50; приведенный в книге пример основан на аналогичном примере Марка Роджерса, взятом с сайта Boost [22].</p>
<p>Разумеется, за материал приложения А следует поблагодарить Мэтта Остерна. Мэтт не только разрешил включить статью в книгу, но и отредактировал ее, чтобы она стала еще лучше оригинала.</p>
<p>Изданию хорошей технической книги всегда предшествует тщательная подготовка. Моей книге повезло — ее просмотрела целая группа выдающихся специалистов. Брайан Керниган (Brian Kerninghan) и Клифф Грин (Cliff Green) прокомментировали ранние наброски, а полную версию книги просматривали Дуг Харрисон, Брайан Керниган, Тим Джонсон (Tim Johnson), Фрэнсис Глассборо (Francis Glassborough), Андрей Александреску, Дэвид Смоллберг, Аарон Кэмпбел (Aaron Campbell), Джаред Мэннинг (Jared Manning), Херб Саттер, Стивен Дью-херст (Stephen Dewhurst), Мэтт Остерн, Гиллмер Дердж (Gillmer Derge), Аарон Мур (Aaron Moore), Томас Бекер (Thomas Becker), Виктор Вон (Victor Von) и, конечно, Марк Роджерс. Редактура была выполнена Катриной Эвери (Katrina Avery).</p>
<p>В процессе подготовки книги очень трудно найти хорошего технического редактора. Я благодарен Джону Поттеру за то, что он познакомил меня с Джаредом Мэннингом и Аароном Кэмпбеллом.</p>
<p>Херб Саттер любезно согласился помочь мне откомпиировать и запустить некоторые тестовые программы STL в бета-версии Microsoft Visual Studio .NET, а Леор Золман (Leor Zolman) взял на себя геркулесов труд по тестированию всего кода в книге. Конечно, все оставшиеся ошибки находятся исключительно на моей ответственности.</p>
<p>Анжелика Лангер открыла мне глаза на неопределенность некоторых аспектов объектов функций STL. В этой книге объектам функций уделяется меньше внимания, чем хотелось бы, но, по крайней мере, все сказанное с большой долей вероятности останется истинным и в будущем. Во всяком случае, я на это надеюсь.</p>
<p>Печатный вариант настоящей книги лучше предыдущих, поскольку внимательные читатели — Джон Уэбб (John Webb), Майкл Хокинс (Michael Hawkins), Дерек Прайс (Derek Price) и Джим Шеллер (Jim Scheller) — указали на некоторые недостатки. Я благодарен им за помощь по улучшению «Effective STL».</p>
<p>Среди моих коллег в издательстве Addison-Wesley были Джон Уэйт Qohn Wait), редактор, а ныне вице-президент, его заместители Алисия Кэри (Alicia Carey) и Сюзанна Бузард (Susannah Buzard), координатор проекта Джон Фуллер (John Fuller), художник Карин Хансен (Karin Hansen), технический гуру Джейсон Джонс (Jason Jones), особенно хорошо разбирающийся в продуктах Adobe, их начальник Марти Рабиновиц (Marty Rabinowitz), а также Курт Джонсон (Curt Johnson), Чанда Лири-Куту (Chanda Leary-Coutu) и Робин Брюс (Robin Bruce) — специалисты по маркетингу, но вполне нормальные люди.</p>
<p>Эбби Стейли (Abby Staley) сделала мои воскресные обеды привычным, но приятным делом.</p>
<p>Как и во время работы над предыдущими шестью книгами и одним компакт-диском, моя жена Нэнси терпеливо сносила мою хроническую занятость и предлагала свою помощь и поддержку именно тогда, когда я в них больше всего нуждался. Она постоянно напоминала мне, что в жизни есть вещи получше С++ и программ.</p>
<p>Остается упомянуть нашу собаку Персефону. В день, когда я пишу эти строки, ей исполняется шесть лет. Сегодня мы с Нэнси и Персефоной отправимся в «Бас-кин-Роббинс». Как обычно, Персефоне достанется один шарик ванильного мороженого в вафельном стаканчике.</p>
</section>
<section>
<title>
<p>От издательства</p>
</title>
<p>Ваши замечания, предложения, вопросы, касающиеся русского издания этой книги, отправляйте по адресу электронной почты <strong><a l:href="mailto:comp@piter.com">comp@piter.com</a></strong> (издательство «Питер», компьютерная редакция).</p>
<p>Мы будем рады узнать ваше мнение!</p>
<p>На web-сайте издательства <strong><a l:href="http://www.piter.com/">http://www.piter.com</a></strong> вы найдете подробную информацию о наших книгак.</p>
</section>
</section>
<section>
<title>
<p>Введение</p>
</title>
<section>
<p>Вы уже знакомы с STL. Вы умеете создавать контейнеры, перебирать их содержимое, добавлять и удалять элементы, а также использовать общие алгоритмы — такие, как find и sort. Но вы никак не можете отделаться от впечатления, что используете лишь малую часть возможностей STL. Задачи, которые должны решаться просто, решаются сложно; операции, которые должны выполняться просто и прямолинейно, приводят к утечке ресурсов или ведут себя непредсказуемо. Процедуры, которые должны работать с максимальной эффективностью, поглощают больше времени или памяти, чем положено. Да, вы умеете использовать библиотеку STL, но не уверены в том, что используете ее <emphasis>эффективно. </emphasis>Я написал эту книгу для вас.</p>
<p>В ней я покажу, как организовать взаимодействие между компонентами STL с тем, чтобы в полной мере использовать архитектуру библиотеки. Вы научитесь разрабатывать простые решения для простых задач и проектировать элегантные решения для более сложных ситуаций. В книге описаны стандартные ошибки использования STL и приведены рекомендации относительно того, как их избежать. Это поможет ликвидировать утечку ресурсов, предотвратить появление непереносимого кода и непредсказуемое поведение программ. Различные приемы оптимизации кода заставят библиотеку STL работать быстро и эффективно, как и было задумано при ее проектировании.</p>
<p>Прочитав эту книгу, вы будете лучше программировать в STL. Программирование станет более продуктивным и интересным занятием. Работать с STL интересно, но эффективная работа с библиотекой — занятие чрезвычайно захватывающее, от которого просто невозможно оторваться. Даже при беглом взгляде на STL становится ясно, что это замечательная библиотека, но ее достоинства гораздо шире и глубже, чем можно себе представить. Я занимаюсь программированием около 30 лет, но я еще никогда не встречал ничего похожего на STL.</p>
</section>
<section>
<title>
<p>Определение, использование и расширение STL</p>
</title>
<p>У STL не существует официального определения, и разные авторы вкладывают в этот термин разный смысл. В этой книге термин «STL» относится к компонентам стандартной библиотеки С++, работающим с итераторами. К этой категории относятся стандартные контейнеры (включая string), части библиотеки потоков ввода-вывода, объекты функций и алгоритмы. В нее не входят адаптеры стандартных контейнеров (<code>stack, queue</code> и <code>priorityqueue</code>), контейнеры <code>bitset</code> и <code>valarray</code>, не поддерживающие итераторы, а также массивы. Хотя массивы поддерживают итераторы в форме указателей, они являются частью <emphasis>языка </emphasis>С++, а не библиотеки.</p>
<p>С технической точки зрения в мое определение STL не входят расширения стандартной библиотеки С++, в том числе хэшированные контейнеры, односвязные списки, контейнеры <code>rope</code> и различные нестандартные объекты функций. Несмотря на это, программистам STL следует помнить о существовании этих расширений, поэтому я упоминаю о них там, где это уместно. В совете 25 приведен обзор нестандартных хэшированных контейнеров. В настоящее время эти контейнеры не входят в STL, но их аналоги почти наверняка будут включены в следующую версию стандартной библиотеки С++.</p>
<p>Возможность расширения была заложена в библиотеку STL во время проектирования. Тем не менее, в этой книге основное внимание уделяется <emphasis>использованию </emphasis>STL, а не разработке новых компонентов. Так, в книге почти ничего не сказано о написании собственных алгоритмов, контейнеров и итераторов. На мой взгляд, сначала необходимо в совершенстве освоить существующие возможности STL. Этой теме и посвящена данная книга. Если вы решите заняться созданием STL-подобных компонентов, необходимую информацию можно будет найти в книгах Джосаттиса «The С++ Standard Library» [3] и Остерна «Generic Programming and the STL» [4]. В этой книге упоминается лишь один аспект расширения STL — написание объектов функций. Эффективное использование STL невозможно без объектов функций, поэтому этой теме в книге посвящена вся глава 6.</p>
</section>
<section>
<title>
<p>Ссылки</p>
</title>
<p>Ссылки на книги Джосаттиса и Остерна в предыдущем абзаце дают представление о том, как в этой книге оформляются библиографические ссылки. Как правило, я стараюсь включить в ссылку достаточно информации, чтобы ее узнали люди, уже знакомые с этим трудом. Если вы уже читали книги этих авторов, вам не придется обращаться к разделу «Литература» и выяснять, что скрывается за ссылками [3] и [4]. Конечно, в списке литературы приведены полные данные.</p>
<p>Три публикации упоминаются так часто, что номер ссылки обычно не указывается. Первая из них, Международный стандарт С++ [5], в книге именуется просто «Стандартом». Две другие — мои предыдущие книги о С++, «Effective С++» [1] и «More Effective С++» [2].</p>
</section>
<section>
<title>
<p>STL и Стандарты</p>
</title>
<p>В книге я часто ссылаюсь на Стандарт С++, потому что основное внимание уделяется переносимой, стандартной версии С++. Теоретически все примеры, приведенные в книге, должны работать во всех реализациях С++. К сожалению, на практике это не так. Вследствие недоработок в компиляторах и реализациях STL даже правильный код иногда не компилируется или работает не так, как положено. В самых типичных случаях описывается суть проблемы и предлагаются обходные решения.</p>
<p>Иногда самый простой выход заключается в переходе на другую реализацию STL (пример приведен в приложении Б). Чем больше вы работаете с STL, тем важнее отличать <emphasis>компилятор </emphasis>от <emphasis>реализации библиотеки. </emphasis>Когда у программиста возникают проблемы с компиляцией правильного кода, он обычно винит во всем компилятор. Однако при работе с STL компилятор может быть в полном порядке, а проблемы оказываются связанными с ошибками в реализации. Чтобы подчеркнуть зависимость программ как от компилятора, так и от реализации библиотеки, я использую термин <emphasis>«платформа STL», </emphasis>под которым понимается комбинация конкретного компилятора с конкретной реализацией STL. Если в книге говорится о проблеме компилятора, значит, виноват именно компилятор. Но если речь идет о проблеме платформы STL, это следует понимать так: «виноват то ли компилятор, то ли библиотека, а может, и то и другое».</p>
<p>Обычно я говорю о компиляторах <emphasis>во множественном числе. </emphasis>Я искренне убежден в том, что проверка работоспособности программы на нескольких компиляторах улучшает ее качество (и особенно переносимость). Более того, использование нескольких компиляторов помогает распутать гордиев узел сообщений об ошибках, выданных при неправильном применении STL (рекомендации по расшифровке этих сообщений приводятся в совете 49).</p>
<p>Уделяя особое внимание тому, чтобы код соответствовал стандартам, я также стремлюсь избежать конструкций с непредсказуемым поведением. Последствия выполнения таких конструкций на стадии работы программы могут быть любыми. К сожалению, иногда эти конструкции могут делать именно то, что требуется, а это создает вредные иллюзии. Слишком многие программисты считают, что непредсказуемое поведение всегда ведет к очевидным проблемам, то есть сбоям обращений к сегментам или другим катастрофическим последствиям. Результаты могут быть гораздо более тонкими (например, искажение данных, редко используемых в программе); кроме того, разные запуски программы могут приводить к разным результатам. У непредсказуемого поведения есть хорошее неформальное определение: «Работает у меня, работает у тебя, работает во время тестирования, но не работает у самого важного клиента». Непредсказуемого поведения следует избегать, поэтому я особо выделяю некоторые стандартные случаи в книге. Будьте начеку и учитесь распознавать ситуации, чреватые непредсказуемым поведением.</p>
</section>
<section>
<title>
<p>Подсчет ссылок</p>
</title>
<p>При описании STL практически невозможно обойти стороной подсчет ссылок. Как будет показано в советах 7 и 33, любая архитектура, основанная на контейнерах указателей, практически всегда основана на подсчете ссылок. Кроме того, подсчет ссылок используется во многих внутренних реализациях <code>string</code>, причем, как показано в совете 15, это обстоятельство иногда приходится учитывать при программировании. Предполагается, что читатель знаком с основными принципами работы механизма подсчета ссылок, а если не знаком — необходимую информацию можно найти в любом учебнике С++ среднего или высокого уровня. Например, в книге «More Effective С++» соответствующий материал приведен в советах 28 и 29. Но даже если вы не знаете, что такое подсчет ссылок, и не горите желанием поскорее узнать, не беспокойтесь. Материал книги в целом все равно останется вполне доступным.</p>
</section>
<section>
<title>
<p><code>string</code> и <code>wstring</code></p>
</title>
<p>Все, что говорится о контейнере <code>string</code>, в равной степени относится и к <code>wstring</code>, его аналогу с расширенной кодировкой символов. Соответственно, любые упоминания о связи между <code>string</code> и <code>char</code> или <code>cha<emphasis>r*</emphasis></code> относятся и к связи между <code>wstring</code> и <code>wchar_t </code>или <code>wchar_t*</code>. Иначе говоря, отсутствие специальных упоминаний о строках с расширенной кодировкой символов не означает, что в STL они не поддерживаются. Контейнеры <code>string</code> и <code>wstring</code> являются специализациями одного шаблона <code>basic_string</code>.</p>
</section>
<section>
<title>
<p>Терминология</p>
</title>
<p>Данная книга не является учебником начального уровня по STL. Предполагается, что читатель уже владеет основными материалом. Тем не менее следующие термины настолько важны, что я счел необходимым особо выделить их.</p>
<p>•Контейнеры vecto<emphasis>r, </emphasis>string, deque и list относятся к категории <emphasis>стандартных последовательных контейнеров. </emphasis>К категории <emphasis>стандартных ассоциативных контейнеров </emphasis>относятся контейнеры set, multiset, map и multimap.</p>
<p>•Итераторы делятся на пять категорий в соответствии с поддерживаемыми операциями. <emphasis>Итераторы ввода </emphasis>обеспечивают доступ только для чтения и позволяют прочитать каждую позицию только один раз. <emphasis>Итераторы вывода </emphasis>обеспечивают доступ только для записи и позволяют записать данные в каждую позицию только один раз. Итераторы ввода и вывода построены по образцу операций чтения-записи в потоках ввода-вывода (например, в файлах), поэтому неудивительно, что самыми распространенными представителями итераторов ввода и вывода являются <code>istream_iterator</code> и <code>ostream_iterator</code> соответственно.</p>
<p><emphasis>Прямые итераторы </emphasis>обладают свойствами итераторов ввода и вывода, но они позволяют многократно производить чтение или запись в любой позиции. Оператор — ими не поддерживается, поэтому они позволяют производить передвижение только в прямом направлении с некоторой степенью эффективности. Все стандартные контейнеры STL поддерживают итераторы, превосходящие эту категорию итераторов по своим возможностям, но, как будет показано в совете 25, одна из архитектур  хэшированных контейнеров основана на использовании прямых итераторов. Контейнеры односвязных списков (см. совет 50) также поддерживают прямые итераторы.</p>
<p><emphasis>Двусторонние итераторы </emphasis>похожи на прямые итераторы, однако они позволяют перемещаться не только в прямом, но и в обратном направлении. Они поддерживаются всеми стандартными ассоциативными контейнерами, а также контейнером <code>list</code>.</p>
<p><emphasis>Итераторы произвольного доступа </emphasis>обладают всеми возможностями двусторонних итераторов, но они также позволяют переходить в прямом или обратном направлении на произвольное расстояние за один шаг. Итераторы произвольного доступа поддерживаются контейнерами <code>vecto<emphasis>r</emphasis></code><emphasis>, </emphasis><code>string</code> и <code>deque</code>. В массивах функциональность итераторов произвольного доступа обеспечивается указателями.</p>
<p>•Любой класс, перегружающий оператор вызова функции (то есть <code>operator<emphasis>()</emphasis></code><emphasis>), </emphasis>является <emphasis>классом функтора. </emphasis>Объекты, созданные на основе таких классов, называются <emphasis>объектами функций, </emphasis>или <emphasis>функторами. </emphasis>Как правило, в STL объекты функций могут свободно заменяться «обычными» функциями, поэтому под термином «объекты функций» часто объединяются как функции С++, так и функторы.</p>
<p>•Функции <code>bind1st</code> и <code>bind2nd</code> называются <emphasis>функциями привязки </emphasis>(binders).</p>
<p>Революционным новшеством STL являются гарантии сложности, то есть ограничения объема работы, выполняемой любыми операциями STL. Таким образом, программист может сравнить относительную эффективность нескольких решений в зависимости от платформы STL. Гарантии сложности выражаются в виде функции от количества элементов в контейнере или интервале <emphasis>(п).</emphasis></p>
<p>•Операция с <emphasis>постоянной сложностью </emphasis>выполняется за время, не зависящее от <emphasis>п. </emphasis>Например, вставка элемента в список выполняется с постоянной сложностью. Сколько бы элементов ни содержал список, один или миллион, вставка будет занимать практически одинаковое время.</p>
<p>Термин «постоянная сложность» не стоит воспринимать буквально. Он означает не то, что время выполнения операции остается строго постоянной величиной, а лишь то, что оно не зависит от <emphasis>п. </emphasis>Например, на двух разных платформах STL время выполнения операции «с постоянной сложностью» может заметно отличаться. Такое бывает, когда одна библиотека использует более совершенную реализацию алгоритма или один компилятор выполняет более активную оптимизацию.</p>
<p>•Операции с <emphasis>логарифмической сложностью </emphasis>с ростом n выполняются за время, пропорциональное логарифму <emphasis>п. </emphasis>Например, операция с миллионом элементов будет выполняться только в три раза дольше операции с сотней элементов, поскольку log n<sup>3</sup> = 3 log n. Многие операции поиска в ассоциативных контейнерах (например, set::find) обладают логарифмической сложностью.</p>
<p>•Время, необходимое для выполнения операций с <emphasis>линейной сложностью, </emphasis>возрастает пропорционально <emphasis>п. </emphasis>Стандартный алгоритм <code>count</code> работает с линейной сложностью, поскольку он должен просмотреть каждый элемент в заданном интервале. Если интервал увеличивается в три раза, объем работы тоже увеличивается втрое, поэтому операция занимает в три раза больше времени.</p>
<p>Как правило, операции с постоянной сложностью выполняются быстрее, чем операции с логарифмической сложностью, а последние выполняются быстрее операций с линейной сложностью. Этот принцип особенно четко выполняется для больших значений <emphasis>п, </emphasis>но при относительно малых n операции, которые теоретически должны занимать больше времени, в отдельных случаях выполняются быстрее. За дополнительной информацией о гарантиях сложности в STL обращайтесь к книге Джосаттиса «The С++ Standard Library» [3].</p>
<p>И последнее замечание по поводу терминологии: вспомните, что каждый элемент контейнеров <code>map</code> и <code>multimap</code> состоит из двух компонентов. Я обычно называю первый компонент <emphasis>ключом, </emphasis>а второй — ассоциированным <emphasis>значением. </emphasis>Например, в контейнере</p>
<p><code>map&lt;string,double&gt; m;</code></p>
<p>ключ относится к типу <code>string</code>, а ассоциированное значение — к типу <code>double</code>.</p>
</section>
<section>
<title>
<p>Примеры</p>
</title>
<p>Книга содержит множество примеров. Все примеры комментируются по мере их приведения, и все же кое-что следует пояснить заранее.</p>
<p>Из приведенного выше примера с <code>map</code> видно, что я обычно опускаю директивы <code>#include</code> и игнорирую тот факт, что компоненты STL принадлежат пространству имен std. Полное определение m должно было выглядеть так:</p>
<p><code>#include &lt;map&gt; </code></p>
<p><code>#include &lt;string&gt;</code></p>
<p><code>using std::map;</code></p>
<p><code>using std::string;</code></p>
<p><code>map&lt;string. double&gt; m;</code></p>
<p>Но я предпочитаю оставить в примере лишь самое существенное. При объявлении формального параметра-типа шаблона вместо <code><strong>class</strong></code> используется ключевое слово <code><strong>typename</strong></code>. Иначе говоря, вместо конструкции вида</p>
<p><code>template &lt;class Т&gt;</code></p>
<p><code>class Widget{...};</code></p>
<p>я использую конструкцию</p>
<p><code>template <strong>&lt;typename </strong>Т&gt;</code></p>
<p><code>class Widget{...};</code></p>
<p>В данном контексте ключевые слова <code>class</code> и <code>typename</code> эквивалентны, но мне кажется, что слово <code>typename</code> более четко выражает важную мысль: подходит <emphasis>любой </emphasis>тип, Т не обязательно является классом. Если вы предпочитаете объявлять параметры с ключевым словом <code>class</code> — пожалуйста. Выбор между <code>typename</code> и <code>class</code> в этом контексте зависит только от стиля.</p>
<p>Однако в других контекстах стиль не является единственным фактором. Во избежание потенциальных неоднозначностей лексического анализа (я избавлю вас от подробностей) имена типов, зависящие от формальных параметров шаблона, должны предваряться ключевым словом <code>typename</code>. Такие типы называются <emphasis>зависимыми типами. </emphasis>Небольшой пример поможет вам лучше понять, о чем идет речь. Предположим, вы пишете шаблон функции, которая получает контейнер STL и возвращает результат проверки условия «последний элемент контейнера больше первого». Одно из возможных решений выглядит так:</p>
<p><code>template &lt;typename С&gt;</code></p>
<p><code>bool latGreaterThanFirst(const С&amp; container)</code></p>
<p><code>{</code></p>
<p><code>if(container.empty()) return false:</code></p>
<p><code><strong>typename </strong>C::const_iterator begin(container.begin());</code></p>
<p><code><strong>typename </strong>C::const_iterator end(container.end());</code></p>
<p><code>return *--end &gt; *begin;</code></p>
<p><code>}</code></p>
<p>В этом примере локальные переменные <code>begin</code> и <code>end</code> относятся к типу <code>С::const_iterator</code><emphasis>, </emphasis>зависящему от формального параметра С. Поскольку тип <code>С:: const_iterator</code> является зависимым, перед ним должно стоять ключевое слово <code>typename</code>. Некоторые компиляторы принимают код без <code>typename</code>, но такой код не переносится на другие платформы.</p>
<p>Надеюсь, вы обратили внимание на жирный шрифт в приведенных примерах. Выделение должно привлечь ваше внимание к особенно важным фрагментам кода. Нередко таким образом подчеркиваются различия между похожими примерами, как, например, при демонстрации двух разных способов объявления параметра Т в примере <code>Widget</code>. Аналогичным образом помечаются и важные блоки на рисунках. Например, на диаграмме из совета 5 таким образом помечаются два указателя, изменяемые при вставке нового элемента в список.</p>
<image l:href="#img_1.png"/>
<empty-line/>
<p>В книге часто встречаются параметры <code>lhs</code> и <code>rhs</code>. Эти сокращения означают «left-hand side» («левая сторона») и «right-hand side» («правая сторона») соответственно, они особенно удобны при объявлении операторов. Пример из совета 19:</p>
<p><code>class Widget {...}:</code></p>
<p><code>bool operator==(const Widget&amp; <strong>lhs</strong><strong>. </strong>const Widgets <strong>rhs):</strong></code></p>
<p>При вызове этой функции в контексте</p>
<p><code>if (х==у)	// Предполагается, что х и у</code></p>
<p><code>		// относятся к классу Widget</code></p>
<p>Объекту х, находящемуся слева от оператора =, в объявлении operator<emphasis>-= </emphasis>соответствует параметр Ihs, а объекту у соответствует параметр rhs.</p>
<p>Что касается имени класса Widge<emphasis>t, </emphasis>то оно не имеет никакого отношения к графическим интерфейсам или инструментариям. Этим именем я привык обозначать «некий класс, который что-то делает». Иногда (как, например, на с. 20) имя Widget относится к шаблону класса, а не к классу. В таких случаях я продолжаю говорить о Widget как о классе несмотря на то, что в действительности это шаблон. Столь неформальное отношение к различиям между классами и шаблонами классов, структурами и шаблонами структур, функциями и шаблонами функций безвредно (при условии, что оно не приводит к возникновению неоднозначности в рассматриваемой теме). Если возможны какие-либо недоразумения, я провожу четкие различия между шаблонами и сгенерированными на их основе классами, структурами и функциями.</p>
</section>
<section>
<title>
<p>Вопросы эффективности</p>
</title>
<p>Сначала я хотел включить в книгу отдельную главу, посвященную вопросам эффективности, но в итоге решил, что лучше оставить привычное деление на советы. Тем не менее многие советы посвящены минимизации затрат памяти и ресурсов на стадии исполнения. Для удобства ниже приводится краткое содержание «виртуальной главы», посвященной эффективности.</p>
<p><strong>Совет 4.Вызывайте empty вместо сравнения size() с нулем</strong></p>
<p><strong>Совет 5.Используйте интервальные функции вместо одноэлементных</strong></p>
<p><strong>Совет 14.Используйте reserve для предотвращения лишних операций перераспределения памяти</strong></p>
<p><strong>Совет 15.Помните о различиях в реализации string</strong></p>
<p><strong>Совет 23.Рассмотрите возможность замены ассоциативных контейнеров сортированными векторами</strong></p>
<p><strong>Совет 24.Тщательно выбирайте между map::operator[] и map::insert</strong></p>
<p><strong>Совет 25.Изучите нестандартные хэшированные контейнеры</strong></p>
<p><strong>Совет 29.Рассмотрите возможность использования istreambuf_iterator при посимвольном вводе</strong></p>
<p><strong>Совет 31.Помните о существовании разных средств сортировки</strong></p>
<p><strong>Совет 44.Используйте функции контейнеров вместо одноименных алгоритмов</strong></p>
<p><strong>Совет 46.Передавайте алгоритмам объекты функций вместо функций</strong></p>
</section>
<section>
<title>
<p>Рекомендации</p>
</title>
<p>Рекомендации, составляющие 50 советов этой книги, основаны на мнениях и наблюдениях опытнейших программистов STL. Они в краткой форме подводят итог всему, что практически всегда следует (или наоборот, не следует) делать для успешного использования библиотеки STL. С другой стороны, это всего лишь рекомендации, и в некоторых ситуациях их нарушения вполне оправданны. Например, в заголовке совета 7 говорится о необходимости вызова <code>delete</code> для указателей перед уничтожением контейнера. Но из текста совета становится ясно, что это правило действует лишь в тех случаях, когда объекты, на которые ссылаются указатели, должны уничтожаться раньше самого контейнера. Обычно это действительно так, но не всегда. Приведу другой пример — в заголовке совета 35 предлагается использовать алгоритмы STL для выполнения простых сравнений строк без учета регистра, но из текста совета следует, что в некоторых случаях лучше использовать функцию не только внешнюю по отношению к STL, но даже не входящую в стандарт С++!</p>
<p>Только хорошее знание специфики программы и условий ее работы позволит определить, стоит ли нарушать представленные рекомендации. Обычно этого лучше не делать, но в отдельных случаях возможны исключения. Как рабская покорность, так и безрассудное легкомыслие одинаково вредны. Прежде чем сходить с проторенной дороги, убедитесь в том, что для этого есть достаточно веские причины.</p>
</section>
</section>
<section>
<title>
<p>Контейнеры</p>
</title>
<section>
<p>В STL входит немало полезных компонентов (в том числе итераторы, алгоритмы и объекты функций), однако большинство программистов С++ ставит на первое место именно контейнеры. По сравнению с массивами контейнеры обладают большей гибкостью и функциональностью. Они динамически увеличивают (а иногда и уменьшают) свои размеры, самостоятельно управляют памятью, следят за количеством хранящихся объектов, ограничивают алгоритмическую сложность поддерживаемых операций и обладают массой других достоинств. Популярность контейнеров STL легко объяснима — просто они превосходят своих конкурентов, будь то контейнеры из других библиотек или самостоятельные реализации. Контейнеры STL не просто хороши. Они <emphasis>действительно </emphasis>хороши.</p>
<p>В этой главе приведены общие сведения, относящиеся ко всем типам контейнеров STL (конкретные типы контейнеров будут рассмотрены в других главах). В частности, мы рассмотрим такие вопросы, как выбор подходящего контейнера при заданных ограничениях; возможность работы кода, написанного для одного типа контейнера, с другими типами контейнеров; особая роль операций копирования объектов в контейнерах; проблемы, возникающие при создании контейнеров с указателями <code>auto_ pt<emphasis>r</emphasis></code><emphasis>; </emphasis>нюансы, связанные с удалением элементов; оптимизация работы с контейнерами и замечания относительно работы контейнеров в многопоточной среде.</p>
<p>Список получился внушительным, но пусть вас это не пугает. Материал излагается небольшими порциями, а попутно вы встретите немало полезных идей, которые сможете <emphasis>немедленно </emphasis>применить в своих программах.</p>
<p>Итак, STL предоставляет в ваше распоряжение множество разных контейнеров, но знаете ли вы, насколько широко это разнообразие? Следующая краткая сводка поможет вам убедиться в том, что вы ни о чем не забыли.</p>
</section>
<section>
<title>
<p>Совет 1. Внимательно подходите к выбору контейнера</p>
</title>
<p>•Стандартные последовательные контейнеры STL: <code>vecto<emphasis>r, </emphasis>string, deque</code> и <code>list</code>.</p>
<p>•Стандартные ассоциативные контейнеры STL: <code>set, multiset, map</code> и <code>multimap</code>.</p>
<p>•Нестандартные последовательные контейнеры: <code>slist</code> и <code>rope</code>. Контейнер <code>slist</code> представляет односвязный список, а <code>rope</code> — строку с дополнительными возможностями. Краткий обзор этих нестандартных (но достаточно широко распространенных) контейнеров приведен в совете 50.</p>
<p>•Нестандартные ассоциативные контейнеры: <code>hash_set, hash_multiset</code>, <code>hash_ map</code> и <code>hash_multimap</code>. Эти популярные разновидности стандартных ассоциативных контейнеров, построенные на базе хэш-таблиц, рассматриваются в совете 25.</p>
<p>•<code>vector&lt;char&gt;</code> как замена для <code>string</code>. Условия, при которых возможна подобная замена, описаны в совете 13.</p>
<p>•<code>vector</code> как замена для стандартных ассоциативных контейнеров. Как будет показано в совете 23, в некоторых ситуациях <code>vector</code> превосходит стандартные ассоциативные контейнеры как по быстродействию, так и по экономии памяти.</p>
<p>•Некоторые стандартные контейнеры, не входящие в STL: массивы, <code>bitset</code>, <code>valarray</code>, <code>stack</code>, <code>queue</code> и <code>piority_queue</code>. Поскольку эти контейнеры не относятся к STL, в этой книге они практически не упоминаются, хотя в совете 16 описан случай, когда массив оказывается предпочтительнее контейнеров SQL, а в совете 18 объясняется, почему <code>bitset</code> может быть лучше <code>vector&lt;bool&gt;</code>. Также стоит помнить о возможности использования массивов с алгоритмами STL, поскольку указатели могут работать как итераторы массивов.</p>
<p>При столь широком ассортименте контейнеров возрастает и количество факторов, которыми следует руководствоваться при их выборе. К сожалению, многие описания STL ограничиваются поверхностным взглядом на мир контейнеров и полностью игнорируют многие факторы, относящиеся к выбору оптимального контейнера. Этот недостаток присущ даже Стандарту, который предлагает выбирать между <code>vecto<emphasis>r, </emphasis>deque</code> и <code>list</code> на основании следующих критериев: «<emphasis>...</emphasis><code><emphasis>vector, list</emphasis></code><emphasis> и </emphasis><code><emphasis>deque</emphasis></code><emphasis> обладают различными характеристиками в зависимости от класса выполняемых операций, в соответствии с которыми должен осуществляться выбор. Вектор (</emphasis><code><emphasis>vector</emphasis></code><emphasis>) представляет собой тип последовательного контейнера, который используется в большинстве случаев. Список (</emphasis><code><emphasis>list</emphasis></code><emphasis>) используется при частых операциях вставки и удаления в произвольной позиции. Дек (</emphasis><code><emphasis>deque</emphasis></code><emphasis>) выбирается в случае, если большинство вставок и удалений производится в начале или в конце последовательности элементов</emphasis>».</p>
<p>Если ограничиться алгоритмической сложностью, эта рекомендация звучит вполне разумно, но на практике приходится учитывать множество других факторов.</p>
<p>Вскоре мы рассмотрим некоторые факторы, учитываемые в дополнение к алгоритмической сложности, но сначала я должен представить критерий классификации контейнеров STL, которому, к сожалению, обычно не уделяется должного внимания. Речь идет о различиях между контейнерами с блоковым и узловым выделением памяти.</p>
<p>В <emphasis>блоковых контейнерах </emphasis>(также называемых <emphasis>контейнерами со смежной памятью) </emphasis>элементы хранятся в одном или нескольких динамически выделяемых блоках памяти, по несколько элементов в каждом блоке. При вставке нового или удалении существующего элемента другие элементы того же блока сдвигаются вверх или вниз, освобождая место для нового элемента или заполняя место, ранее занимаемое удаленным элементом. Подобные перемещения влияют как на скорость работы (советы 5 и 14), так и на безопасность (об этом — ниже). К числу стандартных блоковых контейнеров относятся <code>vecto<emphasis>r</emphasis></code><emphasis>, </emphasis><code>string</code> и <code>deque</code>. Нестандартный контейнер <code>rope</code> также является блоковым.</p>
<p>В <emphasis>узловых контейнерах </emphasis>каждый динамически выделенный фрагмент содержит ровно один элемент. Операции удаления и вставки выполняются только с указателями на узлы, не затрагивая содержимого самих узлов, и потому обходятся без перемещений данных в памяти. К этой категории относятся контейнеры связанных списков (такие как <code>list</code> и <code>slist</code>), а также все стандартные ассоциативные контейнеры, обычно реализуемые в форме сбалансированных деревьев. Как будет показано в совете 25, реализация нестандартных хэшированных контейнеров тоже построена на узловом принципе.</p>
<p>Разобравшись с терминологией, можно переходить к анализу факторов, учитываемых при выборе контейнера. В дальнейшем описании не учитываются контейнеры, не входящие в STL (массивы, битовые множества и т. д.), поскольку книга все-таки посвящена STL.</p>
<p><emphasis>Нужна ли возможность вставки нового элемента в произвольной позиции контейнера? </emphasis>Если нужна, выбирайте последовательный контейнер; ассоциативные контейнеры не подходят.</p>
<p><emphasis>Важен ли порядок хранения элементов в контейнере? </emphasis>Если порядок следования элементов не важен, хэшированные контейнеры попадают в число возможных кандидатов. В противном случае придется обойтись без них.</p>
<p><emphasis>Должен ли контейнер входить в число стандартных контейнеров С++? </emphasis>Если выбор ограничивается стандартными контейнерами, то хэшированные контейнеры, <code>slist</code> и <code>rope</code>, исключаются.</p>
<p><emphasis>К какой категории должны относиться итераторы? </emphasis>С технической точки зрения итераторы произвольного доступа ограничивают ваш выбор контейнерами <code>vecto<emphasis>r, </emphasis>deque</code> и <code>string</code>, хотя, в принципе, можно рассмотреть и возможность применения <code>rope</code> (этот контейнер рассматривается в совете 50). Если нужны двусторонние итераторы, исключается класс slist (совет 50) и одна распространенная реализация хэшированных контейнеров (совет 25).</p>
<p><emphasis>Нужно ли предотвратить перемещение существующих элементов при вставке или удалении? </emphasis>Если нужно, воздержитесь от использования блоковых контейнеров (совет 5).</p>
<p><emphasis>Должна ли структура памяти контейнера соответствовать правилам языка С? </emphasis>Если должна, остается лишь использовать <code>vector</code> (совет 16).</p>
<p><emphasis>Насколько критична скорость поиска? </emphasis>Если скорость поиска критична, рассмотрите хэшированные контейнеры (совет 25), сортированные векторы (совет 23) и стандартные ассоциативные контейнеры — вероятно, именно в таком порядке.</p>
<p><emphasis>Может ли в контейнере использоваться подсчет ссылок? </emphasis>Если подсчет ссылок вас не устраивает, держитесь подальше от <code>string</code>, поскольку многие реализации <code>string</code> построены на этом механизме (совет 13). Также следует избегать контейнера <code>rope</code> (совет 50). Конечно, средства для представления строк вам все же понадобятся — попробуйте использовать <code>vector&lt;cha<emphasis>r&gt;</emphasis></code><emphasis>.</emphasis></p>
<p><emphasis>Потребуется ли транзакционная семантика для операций вставки и удаления? </emphasis>Иначе говоря, хотите ли вы обеспечить надежную отмену вставок и удалений? Если хотите, вам понадобится узловой контейнер. При использовании транзакционной семантики для многоэлементных (например, интервальных — см. совет 5) вставок следует выбрать <code>list</code> — единственный стандартный контейнер, обладающий этим свойством. Транзакционная семантика особенно важна при написании кода, безопасного по отношению к исключениям. Вообще говоря, транзакционная семантика реализуется и для блоковых контейнеров, но за это приходится расплачиваться быстродействием и усложнением кода. За дополнительной информацией обращайтесь к книге Саттера «Exceptional С++» [8].</p>
<p><emphasis>Нужно ли свести к минимуму количество недействительных итераторов, указателей и ссылок? </emphasis>Если нужно — выбирайте узловые контейнеры, поскольку в них операции вставки и удаления никогда не приводят к появлению недействительных итераторов, указателей и ссылок (если они не относятся к удаляемым элементам). В общем случае операции вставки и удаления в блоковых контейнерах могут привести к тому, что все итераторы, указатели и ссылки станут недействительными.</p>
<p><emphasis>Не подойдет ли вам последовательный контейнер с итераторами произвольного доступа, в котором указатели и ссылки на данные всегда остаются действительными, если из контейнера ничего не удаляется, а вставка производится только в конце? </emphasis>Ситуация весьма специфическая, но если вы с ней столкнетесь — выбирайте <code>deque</code>. Следует заметить, что итераторы <code>deque</code>  мо<emphasis>гут </emphasis>стать недействительными, даже если вставка производится только в конце контейнера. Это единственный стандартный контейнер STL, у которого итераторы могут стать недействительными при действительных указателях и ссылках.</p>
<p>Вряд ли эти вопросы полностью исчерпывают тему. Например, в них не учитывается тот факт, что разные типы контейнеров используют разные стратегии выделения памяти (некоторые аспекты этих стратегий описаны в советах 10 и 14). Но и этот список наглядно показывает, что алгоритмическая сложность выполняемых операций — далеко не единственный критерий выбора. Бесспорно, она играет важную роль, но приходится учитывать и другие факторы.</p>
<p>При выборе контейнеров STL предоставляет довольно большое количество вариантов, а за пределами STL их оказывается еще больше. Прежде чем принимать окончательное решение, обязательно изучите <emphasis>все </emphasis>возможные варианты. «...Контейнер, используемый в большинстве случаев»? Я так не думаю.</p>
</section>
<section>
<title>
<p>Совет 2. Остерегайтесь иллюзий контейнерно-независимого кода</p>
</title>
<p>Основным принципом STL является обобщение. Массивы обобщаются в контейнеры, параметризованные по типам хранящихся объектов. Функции обобщаются в алгоритмы, параметризованные по типам используемых итераторов. Указатели обобщаются в итераторы, параметризованные по типам объектов, на которые они указывают.</p>
<p>Но это лишь начало. Конкретные разновидности контейнеров обобщаются в категории (последовательные и ассоциативные), а похожие контейнеры наделяются сходными функциями. Стандартные блоковые контейнеры (совет 1) обладают итераторами произвольного доступа, тогда как стандартные узловые контейнеры (также описанные в совете 1) поддерживают двусторонние итераторы. Последовательные контейнеры поддерживают операции <code>push_front</code> и/или <code>push_back</code>, у ассоциативных контейнеров такие операции отсутствуют. В ассоциативных контейнерах реализованы функции <code>lower_bound</code>, <code>upper_bound</code> и <code>equal_range</code>, обладающие логарифмической сложностью, а в последовательных контейнерах их нет.</p>
<p>При таких тенденциях к обобщению возникает естественная мысль — последовать положительному примеру. Желание похвальное. Несомненно, им стоит руководствоваться при написании собственных контейнеров, итераторов и алгоритмов, но многие программисты пытаются добиться этой цели несколько иным способом. Вместо того чтобы ориентироваться на конкретный тип контейнера, они пытаются обобщить синтаксис так, чтобы в программе, например, использовался <code>vecto<emphasis>r</emphasis></code><emphasis>, </emphasis>но позднее его можно было бы заменить на deque или list без изменения кода, в котором этот контейнер используется. Иначе говоря, они пытаются писать <emphasis>контейнерно-независимый код. </emphasis>Подобные обобщения, какими бы благими намерениями они не были вызваны, почти всегда нежелательны.</p>
<p>Даже самый убежденный сторонник контейнерно-независимого кода вскоре осознает, что универсальный код, работающий как с последовательными, так и с ассоциативными контейнерами, особого смысла не имеет. Многие функции существуют только в контейнерах определенной категории; например, функции <code>push_front</code> и <code>push_back</code> поддерживаются только последовательными контейнерами; функции <code>count</code> и <code>lower_bound</code> — только ассоциативными контейнерами и т. д. Даже сигнатуры таких базовых операций, как <code>insert</code> и <code>erase</code>, зависят от категории. Например, в последовательном контейнере вставленный объект остается в исходной позиции, тогда как в ассоциативном контейнере он перемещается в позицию, соответствующую порядку сортировки данного контейнера. Или другой пример: форма erase, которой при вызове передается итератор, для последовательного контейнера возвращает новый итератор, но для ассоциативного контейнера не возвращается ничего (в совете 9 показано, как это обстоятельство влияет на программный код).</p>
<p>Допустим, вас посетила творческая мысль — написать код, который работал бы со всеми распространенными последовательными контейнерами: <code>vecto<emphasis>r</emphasis></code><emphasis>, </emphasis><code>deque</code> и <code>list</code>. Разумеется, вам придется программировать в контексте общих возможностей этих контейнеров, а значит, функции <code>reserve</code> и <code>capacity</code> (совет 14) использовать нельзя, поскольку они не поддерживаются контейнерами <code>deque</code> и <code>list</code>. Присутствие <code>list</code> также означает, что вам придется отказаться от оператора [] и ограничиться двусторонними итераторами, что исключает алгоритмы, работающие с итераторами произвольного доступа — <code>sort</code>, <code>stable_sort</code>, <code>patial_sort</code> и <code>nth_element</code> (совет 31).</p>
<p>С другой стороны, исходное намерение поддерживать <code>vector</code> исключает функции <code>pushfront</code> и <code>popfont</code>; <code>vector</code> и <code>deque</code> исключают применение <code>splice</code> и реализацию <code>sort</code> внутри контейнера. Учитывая те ограничения, о которых говорилось выше, последний запрет означает, что для вашего «обобщенного последовательного контейнера» не удастся вызвать никакую форму <code>sort</code>.</p>
<p>Пока речь идет о вещах простых и очевидных. При нарушении любого из этих ограничений ваша программа не будет компилироваться по крайней мере для одного из контейнеров, которые вы намеревались поддерживать. Гораздо больше проблем возникнет с программами, которые <emphasis>будут </emphasis>компилироваться.</p>
<p>В разных последовательных контейнерах действуют разные правила недействительности итераторов, указателей и ссылок. Чтобы ваш код правильно работал с <code>vecto<emphasis>r</emphasis></code><emphasis>, </emphasis><code>deque</code> и <code>list</code>, необходимо предположить, что любая операция, приводящая к появлению недействительных итераторов, указателей и ссылок в любом из этих контейнеров, приведет к тем же последствиям и в используемом контейнере. Отсюда следует, что после каждого вызова <code>insert</code> недействительным становится абсолютно все, поскольку <code>deque:: insert</code> делает недействительными все итераторы, а из-за невозможности использования <code>capacity</code> приходится предполагать, что после операции <code>vector:: insert</code> становятся недействительными все указатели и ссылки (как упоминается в совете 1, контейнер <code>deque</code> обладает уникальным свойством — в некоторых случаях его итераторы могут становиться недействительными с сохранением действительных указателей и ссылок). Аналогичные рассуждения приводят к выводу, что после каждого вызова <code>erase</code> все итераторы, указатели и ссылки также должны считаться недействительными.</p>
<p>Недостаточно? Данные контейнера не передаются через интерфейс С, поскольку данная возможность поддерживается только для <code>vector</code> (совет 16). Вы не сможете создать экземпляр контейнера с типом <code>bool</code> — как будет показано в совете 18, <code>vector&lt;bool&gt;</code> не всегда ведет себя как <code>vector</code> и никогда не хранит настоящие логические величины. Вы даже не можете рассчитывать на постоянное время вставки-удаления, характерное для <code>list</code>, поскольку в <code>vector</code> и <code>deque</code> эти операции выполняются с линейной сложностью.</p>
<p>Что же остается после всего сказанного? «Обобщенный последовательный контейнер», в котором нельзя использовать <code>reserve</code>, <code>capacity</code>, <code>operator[]<strong>, </strong>push_front, pop_front, splice</code> и вообще любой алгоритм, работающий с итераторами произвольного доступа; контейнер, у которого любой вызов <code>insert</code> и <code>erase</code> выполняется с линейной сложностью и приводит к недействительности всех итераторов, указателей и ссылок; контейнер, несовместимый с языком С и не позволяющий хранить логические величины. Захочется ли вам использовать подобный контейнер в своем приложении? Вряд ли.</p>
<p>Если умерить амбиции и отказаться от поддержки <code>list</code>, вы все равно теряете <code>reserve</code>, <code>capacity</code>, <code>push_front</code> и <code>pop_front</code>; вам также придется полагать, что вызовы <code>insert</code> и <code>erase</code> выполняются с линейной сложностью, а все итераторы, указатели и ссылки становятся недействительными; вы все равно теряете совместимость с С и не можете хранить в контейнере логические величины.</p>
<p>Даже если отказаться от последовательных контейнеров и взяться за ассоциативные контейнеры, дело обстоит не лучше. Написать код, который бы одновременно работал с <code>set</code> и <code>map</code>, практически невозможно, поскольку в <code>set</code> хранятся одиночные объекты, а в <code>map</code> хранятся пары объектов. Даже совместимость с <code>set</code> и <code>multiset</code> (или <code>map</code> и <code>multimap</code>) обеспечивается с большим трудом. Функция <code>insert</code>, которой при вызове передается только значение вставляемого элемента, возвращает разные типы для <code>set/map</code> и их multi-аналогов, при этом вы должны избегать любых допущений относительно того, сколько экземпляров данной величины хранится в контейнере. При работе с <code>map</code> и <code>multimap</code> приходится обходиться без оператора [ ], поскольку эта функция существует только в <code>map</code>.</p>
<p>Согласитесь, игра не стоит свеч. Контейнеры действительно <emphasis>отличаются </emphasis>друг от друга, обладают разными достоинствами и недостатками. Они не были рассчитаны на взаимозаменяемость, и с этим фактом остается только смириться. Любые попытки лишь искушают судьбу, а она этого не любит.</p>
<p>Но рано или поздно наступит день, когда окажется, что первоначальный выбор контейнера был, мягко говоря, не оптимальным, и вы захотите переключиться на другой тип. При изменении типа контейнера нужно не только исправить ошибки, обнаруженные компилятором, но и проанализировать весь код, где он используется, и разобраться, что следует изменить в свете характеристик нового контейнера и правил перехода итераторов, указателей и ссылок в недействительное состояние. Переходя с <code>vector</code> на другой тип контейнера, вы уже не сможете рассчитывать на С-совместимую структуру памяти, а при обратном переходе нужно проследить за тем, чтобы контейнер не использовался для хранения <code>bool</code>.</p>
<p>Если вы знаете, что тип контейнера в будущем может измениться, эти изменения можно упростить обычным способом — инкапсуляцией. Одно из простейших решений основано на использовании определений <code>typedef</code> для типов контейнера и итератора. Следовательно, фрагмент</p>
<p><code>class Widget{...};</code></p>
<p><code>vector&lt;Widget&gt; vw;</code></p>
<p><code>Widget  bestWidget;</code></p>
<p><code>… 	// Присвоить значение bestWidget</code></p>
<p><code>vector&lt;Widget&gt;::iterator i =// Найти Widget с таким же значением,</code></p>
<p><code>find(vw.begin(),vw.end().bestWidget) // как у bestWidget</code></p>
<p>записывается в следующем виде:</p>
<p><code>class Widget{...};</code></p>
<p><code><strong>typedef vector&lt;Widget&gt; WidgetContaner;</strong></code></p>
<p><code><strong>typedef WidgetContainer:</strong>.i<strong>terator WCIterator;</strong></code></p>
<p><code><strong>WidgetContaner </strong>vw;</code></p>
<p><code>Widget bestWidget;</code></p>
<p><code><strong>WCIterator </strong>i =find(vw.begin().vw.end(),bestWidget):</code></p>
<p>Подобная запись значительно упрощает изменение типа контейнера, что особенно удобно, когда изменение сводится к простому добавлению нестандартного распределителя памяти (такое изменение не влияет на правила недействительности итераторов/указателей/ссылок).</p>
<p><code>class Widget{...};</code></p>
<p><code><strong>template&lt;typename T&gt;</strong>// В совете 10 объясняется, почему</code></p>
<p><code><strong>Specia1Anocator{.</strong>..}; // необходимо использовать шаблон</code></p>
<p><code>typedef vector&lt;Widget<strong>.Specia1Anocator&lt;Widget»</strong> WidgetContainer;</code></p>
<p><code>typedef WidgetContainer::iterator WCIterator;</code></p>
<p><code>WidgetContainer vw;// Работает</code></p>
<p><code>Widget bestWidget;</code></p>
<p><code><strong>WCIterator </strong>i=find(vw.begin().vw.end().bestWidget); // Работает</code></p>
<p>Даже если вас не интересуют аспекты <code>typedef</code>, связанные с инкапсуляцией, вы наверняка оцените экономию времени. Предположим, у вас имеется объект типа</p>
<p><code>map&lt;string,</code></p>
<p><code>	vector&lt;Widget&gt;::iterator,</code></p>
<p><code>	CIStringCompare&gt;// ClStringCompare - сравнение строк</code></p>
<p><code>			// без учета регистра: см. совет 19</code></p>
<p>и вы хотите перебрать элементы множества при помощи <code>const_iterator</code>. Захочется ли вам вводить строку</p>
<p><code>map&lt;string.vector&lt;Widget&gt;::iterator,CIStringCompare&gt;::const_iterator</code></p>
<p>больше одного раза? После непродолжительной работы в STL вы поймете, что <code>typedef</code> — ваш друг.</p>
<p><code>Typedef</code> всего лишь определяет синоним для другого типа, поэтому инкапсуляция производится исключительно на лексическом уровне. Она не помешает клиенту сделать то, что он мог сделать ранее (и не позволит сделать то, что было ранее недоступно). Если вы захотите ограничить зависимость клиента от выбранного типа контейнера, вам понадобятся более серьезные средства — классы.</p>
<p>Чтобы ограничить объем кода, требующего модификации при замене типа контейнера, скройте контейнер в классе и ограничьте объем информации, доступной через интерфейс класса. Например, если вам потребуется создать список клиентов, не используйте класс <code>list</code> напрямую, определите класс <code>CustomerList</code> и инкапсулируйте <code>list</code> в его закрытой части:</p>
<p><code>class CustomerList {</code></p>
<p><code>private:</code></p>
<p><code>	typedef list&lt;Customer&gt; CustomerContainer;</code></p>
<p><code>	typedef CustomerContainer::iterator CCIterator;</code></p>
<p><code>	CustomerContainer customers:</code></p>
<p><code>public:	// Объем информации, доступной</code></p>
<p><code>		// через этот интерфейс, ограничивается</code></p>
<p><code>};</code></p>
<p>На первый взгляд происходящее выглядит глупо. Ведь список клиентов — это <emphasis>список, </emphasis>не правда ли? Вполне возможно. Но в будущем может оказаться, что возможность вставки-удаления в середине списка используется не так часто, как</p>
<p>предполагалось вначале, зато нужно быстро выделить 20% клиентов с максимальным объемом сделок — эта задача просто создана для алгоритма <code>nthelement</code> (совет 31). Однако <code>nthelement</code> требует итератора произвольного доступа и не будет работать с контейнером <code>list</code>. В этой ситуации «список» лучше реализовать на базе <code>vector</code> или <code>deque</code>.</p>
<p>Рассматривая подобные изменения, необходимо проанализировать все функции класса <code>CustomerList</code>, а также всех «друзей» (<code>friend</code>) и посмотреть, как на них отразится это изменение (в отношении быстродействия, недействительности итераторов/указателей/ссылок и т. д.), но при грамотной инкапсуляции деталей реализации <code>CustomerList</code> это изменение практически не повлияет на клиентов <code>CustomerList</code>.</p>
</section>
<section>
<title>
<p>Совет 3. Реализуйте быстрое и корректное копирование объектов в контейнерах</p>
</title>
<p>В контейнерах хранятся объекты, но не те, которые вы им передаете. Более того, при получении объекта из контейнера вам предоставляется не тот объект, который находился в контейнере. При включении объекта (вызовом <code>insert, push_back</code> и т. д.) в контейнер заносится <emphasis>копия </emphasis>указанного объекта. При получении объекта из контейнера (например, вызовом <code>front</code> или <code>back</code>) вы также получаете <emphasis>копию. </emphasis>Копирование на входе, копирование на выходе — таковы правила STL.</p>
<p>Но и после того, как объект окажется в контейнере, он может участвовать в операциях копирования. В результате вставки или удаления элементов в <code>vector, string</code> и <code>deque</code> существующие элементы контейнера обычно перемещаются (копируются) в памяти (советы 5 и 14). Алгоритмы сортировки (совет 31), <code>next_permutation</code> и <code>previous_permutation</code>; <code>remove</code>, <code>unique</code> и их родичи (совет 32); <code>rotate</code> и <code>reverse</code> — все эти операции приводят к копированию объектов. Да, копирование объектов действительно занимает очень важное место в STL.</p>
<p>Возможно, вам будет интересно узнать, как же производится копирование. Очень просто — объект копируется вызовом соответствующих функций этого объекта, а точнее <emphasis>копирующего </emphasis>конструктора и <emphasis>копирующего </emphasis>оператора присваивания. В пользовательских классах эти функции обычно объявляются следующим образом:</p>
<p><code>class Widget{ public:</code></p>
<p><code>Widget(const Widget&amp;):// Копирующий конструктор</code></p>
<p><code>Widget&amp; operator=(const Widget&amp;);// Копирующий оператор присваивания</code></p>
<p><code><strong>}: "'</strong></code></p>
<p>Как обычно, если вы не объявите эти функции самостоятельно, компилятор сделает это за вас. Встроенные типы (<code>int</code>, указатели и т. д.) копируются простым копированием их двоичного представления. Копирующие конструкторы и операторы присваивания описаны в любом учебнике по С++. В частности, эти функции рассмотрены в советах 11 и 27 книги «Effective С++».</p>
<p>Теперь вам должен быть ясен смысл этого совета. Если контейнер содержит объекты, копирование которых сопряжено с большими затратами, простейшее занесение объектов в контейнер может заметно повлиять на скорость работы программы. Чем больше объектов перемещается в контейнере, тем больше памяти и тактов процессора расходуется на копирование. Более того, у некоторых объектов само понятие «копирование» имеет нетрадиционный смысл, и при занесении таких объектов в контейнер неизменно возникают проблемы (пример приведен в совете 8).</p>
<p>В ситуациях с наследованием копирование становится причиной отсечения. Иначе говоря, если создать контейнер объектов базового класса и попытаться вставить в него объекты производного класса, «производность» этих объектов утрачивается при копировании объектов (копирующим конструктором базового класса) в контейнер:</p>
<p><code>vector&lt;Widget&gt; vw;</code></p>
<p><code>class Special Widget:// SpecialWidget наследует от класса</code></p>
<p><code>public Widget{...};// Widget (см. ранее)</code></p>
<p><code>SpecialWidget sw;// sw копируется в vw как <emphasis>объект базового класса</emphasis></code></p>
<p><code>vw.push_back(sw);// Специализация объекта теряется (отсекается)</code></p>
<p>Проблема отсечения предполагает, что вставка объекта производного класса в контейнер объектов базового класса обычно приводит к ошибке. А если вы хотите, чтобы полученный объект обладал <emphasis>поведением </emphasis>объекта производного класса (например, вызывал виртуальные функции объектов производного класса), вставка <emphasis>всегда </emphasis>приводит к ошибке. За дополнительной информацией обращайтесь к «Effective С++», совет 22. Другой пример проявления этой проблемы в STL описан в совете 38.</p>
<p>Существует простое решение, обеспечивающее эффективное, корректное и свободное от проблемы отсечения копирование — вместо объектов в контейнере хранятся <emphasis>указатели. </emphasis>Иначе говоря, вместо контейнера для хранения <code>Widget</code> создается контейнер для <code>Widget*</code>. Указатели быстро копируются, результат точно совпадает с ожидаемым (поскольку копируется базовое двоичное представление), а при копировании указателя ничего не отсекается. К сожалению, у контейнеров указателей имеются свои проблемы, обусловленные спецификой STL. Они рассматриваются в советах 7 и 33. Пытаясь справиться с этими проблемами и при этом не нажить хлопот с эффективностью, корректностью и отсечением, вы, вероятно, обнаружите симпатичную альтернативу — <emphasis>умные указатели. </emphasis>За дополнительной информацией обращайтесь к совету 7.</p>
<p>Если вам показалось, что STL злоупотребляет копированием, не торопитесь с выводами. Да, копирование в STL выполняется довольно часто, но в целом библиотека спроектирована с таким расчетом, чтобы избежать лишнего копирования. Более того, она избегает лишнего <emphasis>создания </emphasis>объектов. Сравните с поведением классического массива — единственного встроенного контейнера С и С++:</p>
<p><code>Widget w[maxNumWidgets]; // Создать массив объектов Widget</code></p>
<p><code>	// Объекты инициализируются конструктором </code></p>
<p><code>	// по умолчанию</code></p>
<p>В этом случае конструируются <code>maxNumWidgets</code> объектов <code>Widget</code>, даже если на практике будут использоваться лишь некоторые из них или все данные, инициализированные конструктором по умолчанию, будут немедленно перезаписаны данными, взятыми из другого источника (например, из файла). Вместо массива можно воспользоваться контейнером STL <code>vector</code> и создать вектор, динамически увеличивающийся в случае необходимости:</p>
<p><code>vector&lt;Widget&gt; vw: // Создать вектор, не содержащий ни одного </code></p>
<p><code>	// объекта Widget и увеличивающийся по мере </code></p>
<p><code>	// необходимости</code></p>
<p>Можно также создать пустой вектор, в котором зарезервировано место для <code>maxNumWidgets</code> объектов <code>Widget</code>, но не сконструирован ни один из этих объектов:</p>
<p><code>vector&lt;Widget&gt; vw:</code></p>
<p><code>vw.reserve(maxNumWidgets): // Функция reserve описана в совете 14</code></p>
<p>По сравнению с массивами контейнеры STL ведут себя гораздо цивилизованнее. Они создают (посредством копирования) столько объектов, сколько указано, и только по вашему требованию, а конструктор по умолчанию выполняется только с вашего разрешения. Да, контейнеры STL создают копии; да, в особенностях их работы необходимо хорошо разбираться, но не стоит забывать и о том, что они означают большой шаг вперед по сравнению с массивами.</p>
</section>
<section>
<title>
<p>Совет 4. Вызывайте empty вместо сравнения size() с нулем</p>
</title>
<p>Для произвольного контейнера с следующие две команды фактически эквивалентны:</p>
<p><code>if (c.size()==0)...</code></p>
<p><code>if (c.empty())...</code></p>
<p>Возникает вопрос — почему же предпочтение отдается одной конструкции, особенно если учесть, что <code>empty</code> обычно реализуется в виде подставляемой (inline) функции, которая просто сравнивает <code>size()</code> с нулем и возвращает результат?</p>
<p>Причина проста: функция <code>empty</code> для всех стандартных контейнеров выполняется с постоянной сложностью, а в некоторых реализациях <code>list</code> вызов <code>size</code> требует линейных затрат времени.</p>
<p>Но почему списки так себя ведут? Почему они не обеспечивают выполнения <code>size</code> с постоянной сложностью? Это объясняется в основном уникальными свойствами функций врезки (<code>splicing</code>). Рассмотрим следующий фрагмент:</p>
<p><code>list&lt;int&gt; list1;</code></p>
<p><code>list<emphasis>&lt;i</emphasis>nt&gt; list2;</code></p>
<p><code>list1.splice(		// Переместить все узлы list2</code></p>
<p><code>   list1.end(),list2,	// от первого вхождения 5</code></p>
<p><code>find(list2.begin(),list2.end(), 5),// до последнего вхождения 10</code></p>
<p><code>find(list2.rbegin().list2.rend(),10).base()// в конец listl</code></p>
<p><code>);// Вызов base() рассматривается</code></p>
<p><code>// в совете 28</code></p>
<p>Приведенный фрагмент не работает, если только значение 10 не входит в list2 после 5, но пока не будем обращать на это внимания. Вместо этого зададимся вопросом: сколько элементов окажется в списке list1 после врезки? Разумеется, столько, сколько было до врезки, в сумме с количеством новых элементов. Последняя величина равна количеству элементов в интервале, определяемом вызовами <code>find(list2.begin(),list2.end(), 5) и find(list2.rbegin(),list2.rend(),10).base()</code>. Сколько именно? Чтобы ответить на этот вопрос, нужно перебрать и подсчитать элементы интервала. В этом и заключается проблема.</p>
<p>Допустим, вам поручено реализовать <code>list</code>. Это не просто контейнер, а <emphasis>стандартный </emphasis>контейнер, поэтому заранее известно, что класс будет широко использоваться. Естественно, реализация должна быть как можно более эффективной. Операция определения количества элементов в списке будет часто использоваться клиентами, поэтому вам хотелось бы, чтобы операция <code>size</code> работала с постоянной сложностью. Класс <code>list</code> нужно спроектировать так, чтобы он всегда знал количество содержащихся в нем элементов.</p>
<p>В то же время известно, что из всех стандартных контейнеров только <code>list</code> позволяет осуществлять врезку элементов без копирования данных. Можно предположить, что многие клиенты выбирают <code>list</code> именно из-за эффективности операции врезки. Они знают, что интервальная врезка из одного списка в другой выполняется за постоянное время; вы знаете, что они это знают, и постараетесь не обмануть их надежды на то, что функция <code>splice</code> работает с постоянными затратами времени.</p>
<p>Возникает дилемма. Чтобы операция size выполнялась с постоянной сложностью, каждая функция класса list должна обновлять размеры списков, с которыми она работает. К числу таких функций относится и splice. Но сделать это можно только одним способом — функция должна подсчитать количество вставляемых элементов, а это не позволит обеспечить постоянное время выполнения splice... чего мы, собственно, и пытались добиться. Если отказаться от обновления размеров списков функцией splice, добиться постоянного времени выполнения для <code>splice</code> можно, но тогда с линейной сложностью будет выполняться <code>size</code> — ей придется перебирать всю структуру данных и подсчитывать количество элементов. Как ни старайся, чем-то — size или splice — придется пожертвовать. Одна из этих операций может выполняться с постоянной сложностью, но не обе сразу.</p>
<p>В разных реализациях списков эта проблема решается разными способами в зависимости от того, какую из операций — <code>size</code> или <code>splice</code> — авторы хотят оптимизировать по скорости. При работе с реализацией <code>list,</code> в которой было выбрано постоянное время выполнения <code>splice</code>, лучше вызывать <code>empty</code> вместо <code>size</code>, поскольку <code>empty</code> всегда работает с постоянной скоростью. Впрочем, даже если вы не используете такую реализацию, не исключено, что это произойдет в будущем. Возможно, программа будет адаптирована для другой платформы с другой реализацией STL, или вы перейдете на новую реализацию STL для текущей платформы.</p>
<p>В любом случае вы ничем не рискуете, вызывая <code>empty</code> вместо проверки условия <code>size()=0</code>. Мораль: если вам потребовалось узнать, содержит ли контейнер ноль элементов — вызывайте <code>empty</code>. .</p>
</section>
<section>
<title>
<p>Совет 5. Используйте интервальные функции вместо одноэлементных</p>
</title>
<p>Есть два вектора, v1 и v2. Как проще всего заполнить v1 содержимым второй половины v2? Только не надо мучительно размышлять над тем, что считать «половиной» при нечетном количестве элементов в v2. Просто постарайтесь быстро дать разумный ответ.</p>
<p>Время истекло! Если вы предложили</p>
<p><code>v1.assign(v2.begin()+v2.size()/2,v2.end())</code></p>
<p>или нечто похожее — поздравляю, пять баллов. Если в вашем ответе присутствуют вызовы более чем одной функции, но при этом он обходится без циклов, вы получаете «четверку». Если в ответе задействован цикл, вам есть над чем поработать, а если несколько циклов — значит, вы узнаете из этой книги много нового.</p>
<p>Кстати говоря, если при чтении <emphasis>ответа </emphasis>вы произнесли «Чего-чего?» или что-нибудь в этом роде, читайте внимательно, потому что речь пойдет об очень полезных вещах.</p>
<p>Я привел эту задачу по двум причинам. Во-первых, она напоминает вам о существовании очень удобной функции <code>assign</code>, о которой многие программисты попросту забывают. Функция <code>assign</code> поддерживается всеми стандартными последовательными контейнерами (<code>vector, string, deque</code> и <code>list</code>). Каждый раз, когда вам требуется полностью заменить содержимое контейнера, подумайте, нельзя ли добиться желаемой цели присваиванием. Если вы просто копируете один контейнер в другой контейнер того же типа, задача решается функцией <code>operator=</code>. Но, как показывает приведенный пример, существует также функция <code>assign</code>, которая позволяет заполнить контейнер новыми данными в тех случаях, когда <code>operator=</code> не подходит.</p>
<p>Во-вторых, эта задача показывает, почему интервальные функции лучше своих одноэлементных аналогов. <emphasis>Интервальной </emphasis>называется функция контейнера, которая, подобно алгоритмам STL, определяет интервал элементов для выполняемой операции при помощи двух параметров-итераторов. Без интервальной функции нам пришлось бы создавать специальный цикл:</p>
<p><code>vector&lt;Widget&gt; v1,v2; // Предполагается, что v1 и v2 -</code></p>
<p><code>		// векторы объектов Widget</code></p>
<p><code>vl.clear():</code></p>
<p><code>for (vector&lt;Widget&gt;::const_iterator ci=v2.begin()+v2.size()/2;</code></p>
<p><code>	ci != v2.end();</code></p>
<p><code>	++ci)</code></p>
<p><code>v1.push_back(*c<emphasis>i):</emphasis></code></p>
<p>В совете 43 подробно объясняется, почему использовать явные циклы не рекомендуется, но и без этого ясно, что написание этого фрагмента потребует больше усилий, чем простой вызов <code>assign</code>. Цикл также отрицательно влияет на быстродействие, но к этой теме мы вернемся позже.</p>
<p>Одно из возможных решений заключается в том, чтобы последовать совету 43 и воспользоваться алгоритмом:</p>
<p><code>vl.clear();</code></p>
<p><code>copy(v2.begin()+v2.size()/2.v2.end().back_inserter(v1));</code></p>
<p>Но и этот вариант требует больших усилий, чем простой вызов <code>assign</code>. Более того, хотя цикл не встречается в программе, он наверняка присутствует внутри вызова <code>сору</code> (см. совет 43). В результате потенциальное снижение быстродействия не исчезает (вскоре мы поговорим об этом). А сейчас я хочу ненадолго отвлечься от темы и заметить, что практически все случаи использования сору, когда приемный интервал задается итератором вставки (<code>inserter, back_inserter</code> или <code>front_inserter</code>), могут — <emphasis>и должны — </emphasis>заменяться вызовами интервальных функций. Например, вызов сору заменяется интервальной версией <code>insert</code>:</p>
<p><code>vl.insert(vl.end(),v2.begin()+v2.size()/2.v2.end());</code></p>
<p>Команда получается ненамного короче, но она к тому же ясно указывает на суть происходящего: данные вставляются в v1. Вызов <code>сору</code> означает примерно то же, но не столь очевидно. В данном случае важно не то, что элементы копируются, а то, что в <code>v1</code> добавляются новые данные. Функция <code>insert</code> прямо говорит об этом, а сору лишь сбивает с толку. Нет ничего особенно интересного в том факте, что данные где-то копируются, — собственно, вся библиотека STL <emphasis>построена </emphasis>на принципе копирования. Копирование играет настолько важную роль в STL, что ему посвящен совет 3.</p>
<p>Многие программисты STL злоупотребляют функцией <code>сору</code>, поэтому только что данный совет стоит повторить: вызовы сору, в которых результирующий интервал задается итератором вставки, практически всегда следует заменять вызовами интервальных функций.</p>
<p>Вернемся к примеру с <code>assign</code>. Мы уже выяснили две причины, по которым интервальным функциям отдается предпочтение перед их одноэлементными аналогами.</p>
<p>•Написание кода с интервальными функциями обычно требует меньших усилий.</p>
<p>•Решения с интервальными функциями обычно выглядят более наглядно и логично.</p>
<p>Короче говоря, программы с интервальными функциями удобнее как писать, так и читать. О чем тут еще говорить?</p>
<p>Впрочем, некоторые склонны относить эти аргументы к стилю программирования, а вопросы стиля вызывают у программистов такую же жаркую полемику, как и тема выбора Лучшего В Мире Редактора (хотя о чем тут спорить? Всем известно, что это <code>Emacs</code>). Было бы неплохо иметь более универсальный критерий для сравнения интервальных функций с одноэлементными. Для стандартных последовательных контейнеров такой критерий существует: эффективность. При работе со стандартными последовательными контейнерами применение одноэлементных функций приводит к более частому выделению памяти, более частому копированию объектов и/или выполнению лишних операций по сравнению с реализацией, основанной на интервальных функциях.</p>
<p>Предположим, вы хотите скопировать массив <code>int</code> в начало <code>vector</code> (исходное размещение данных в массиве может объясняться тем, что данные были получены через унаследованный интерфейс с языком С. Проблемы, возникающие при объединении контейнеров STL с интерфейсом С, описаны в совете 16). Решение с интервальной функцией <code><strong>insert</strong></code> контейнера <code><strong>vector</strong></code> выглядит просто и бесхитростно:</p>
<p><code>int data[numValues];// Предполагается, что numValues</code></p>
<p><code>// определяется в другом месте</code></p>
<p><code>vector&lt;int&gt; v:</code></p>
<p><code>v.insert(v.begin().data,data+numValues): // Вставить int из data</code></p>
<p><code>// в начало v</code></p>
<p>Вероятно, решение с циклическим вызовом <code>insert</code> выглядит примерно так:</p>
<p><code>vector&lt;int&gt;::iterator insertLoc(v.begin());</code></p>
<p><code>for(int i=0;i&lt;numValues;++i) {</code></p>
<p><code>insertLoc = v.insert(insertLoc.data[i]);</code></p>
<p><code>}</code></p>
<p>Обратите внимание на сохранение значения, возвращаемого при вызове <code><strong>insert</strong></code><strong>, </strong>до следующей итерации. Если бы значение <code><strong>insertLoc</strong></code> не обновлялось после каждой вставки, возникли бы две проблемы. Во-первых, все итерации цикла после первой повели бы себя непредсказуемым образом, поскольку в результате каждого вызова <code><strong>insert</strong></code> значение <code><strong>insertLoc</strong></code> становилось бы недействительным. Во-вторых, даже если бы значение <code><strong>insertLoc</strong></code> оставалось действительным, вставка всегда производилась бы в начале вектора (то есть в <code><strong>v.begin()</strong></code>)<strong>, </strong>и в результате содержимое массива было бы скопировано в обратном порядке.</p>
<p>Попробуем последовать совету 43 и заменим цикл вызовом сору:</p>
<p><code>copy(data.data+numValues.inserter(v.v.begin()));</code></p>
<p>После создания экземпляра шаблона решение с <code>сору</code> практически идентично решению с циклом, поэтому в своем анализе эффективности мы ограничимся вторым вариантом и будем помнить, что все сказанное в равной степени относится к решению с сору. В случае с циклом вам будет проще понять, чем обусловлены потери эффективности. Да, это именно «потери» во множественном числе, поскольку решение с одноэлементной версией <strong>insert </strong>сопряжено с тремя видами затрат, отсутствующими при использовании интервальной версии <strong>insert.</strong></p>
<p>Первая потеря обусловлена лишними вызовами функций. Естественно, последовательная вставка <strong>numValues </strong>элементов требует <strong>numValues </strong>вызовов <strong>insert. </strong>При вызове интервальной формы <strong>insert </strong>достаточно одного вызова функции, тем самым экономится <strong>numValues</strong>-1 вызов. Возможно, подстановка <strong>(inlining) </strong>избавит вас от этих затрат... а может, и нет. Уверенным можно быть лишь в одном: при использовании интервальной формы <strong>insert </strong>эти затраты заведомо отсутствуют.</p>
<p>Подстановка не спасает от второго вида затрат, обусловленных неэффективностью перемещения существующих элементов <strong>v </strong>на итоговые позиции после вставки. Каждый раз, когда <strong>insert </strong>включает в <strong>v </strong>новый элемент, все элементы после точки вставки смещаются на одну позицию, освобождая место. Элемент в позиции p перемещается в позицию <emphasis>р+1 </emphasis>и т. д. В нашем примере <code><strong>numValues</strong></code> элементов вставляются в начало <strong>v. </strong>Следовательно, каждый элемент, находившийся в <strong>v </strong>до вставки, сдвигается в общей сложности на <strong>numValues </strong>позиций. Но при каждом вызове <strong>insert </strong>элемент сдвигается только на одну позицию, поэтому это потребует <strong>numValues </strong>перемещений. Если до вставки вектор <strong>v </strong>содержал n элементов, количество перемещений будет равно n<strong>*numValues. </strong>В нашем примере вектор <strong>v </strong>содержит числа типа <code>int</code><emphasis>, </emphasis>поэтому перемещение сведется к простому вызову <strong>memmove, </strong>но если бы в <strong>v </strong>хранились пользовательские типы вроде <strong>Widget, </strong>то каждое перемещение было бы сопряжено с вызовом оператора присваивания или копирующего конструктора данного типа (в большинстве случаев вызывался бы оператор присваивания, но перемещения последнего элемента вектора обеспечивались бы вызовом копирующего конструктора). Таким образом, в общем случае последовательная вставка <strong>numValues </strong>новых объектов в начало <code><strong>vector&lt;Widget&gt;</strong></code> с n элементами требует n<strong>*numValues </strong>вызовов функций: <strong>(n-l)*numValues </strong>вызовов оператора присваивания <strong>Widget </strong>и <strong>numValues </strong>вызовов копирующего конструктора <strong>Widget. </strong>Даже если эти вызовы будут подставляемыми, все равно остаются затраты на перемещение элементов <strong>numValues </strong>раз.</p>
<p>С другой стороны, Стандарт требует, чтобы интервальные функции <strong>insert </strong>перемещали существующие элементы контейнера непосредственно в итоговые позиции, то есть по одному перемещению на элемент. Общие затраты составят n перемещений <strong>(numValues </strong>для копирующего конструктора типа объектов в контейнере, остальное — для оператора присваивания этого типа). По сравнению с одноэлементной версией интервальная версия <strong>insert </strong>выполняет на n<strong>*(numValues-l) </strong>меньше перемещений. Только задумайтесь: при <strong>numValues</strong>=100 интервальная форма <strong>insert </strong>выполняет на 99% меньше перемещений, чем эквивалентный код с многократно повторяющимися вызовами одноэлементной формы <strong>insert!</strong></p>
<p>Прежде чем переходить к третьей категории затрат, стоит сделать небольшое замечание. То, что написано в предыдущем абзаце — правда, только правда и ничего, кроме правды, но это <emphasis>не вся </emphasis>правда. Интервальная форма <strong>insert </strong>может переместить элемент в конечную позицию за одну операцию только в том случае, если ей удастся определить расстояние между двумя итераторами без перехода. Это возможно почти всегда, поскольку такой возможностью обладают все прямые итераторы, а они встречаются практически повсеместно. Все итераторы стандартных контейнеров обладают функциональными возможностями прямых итераторов — в том числе и итераторы нестандартных хэшированных контейнеров (совет 25). Указатели, играющие роль итераторов в массивах, тоже обладают этой возможностью. В общем-то, из всех стандартных итераторов она не присуща только итераторам ввода и вывода. Следовательно, все сказанное выше справедливо в том случае, если итераторы, передаваемые интервальной форме <strong>insert, </strong>не являются итераторами ввода (скажем, <code><strong>istream_iterator</strong></code> — см. совет 6). Только в этом случае интервальной форме <strong>insert </strong>приходится перемещать элементы на свои итоговые места по одной позиции, вследствие чего преимущества интервальной формы теряются (для итераторов вывода эта проблема вообще не возникает, поскольку итераторы вывода не могут использоваться для определения интервала <strong>insert).</strong></p>
<p>Мы подошли к третьей категории затрат, от которых страдают неразумные программисты, использующие многократную вставку отдельного элемента вместо одной вставки целого интервала. Эти затраты связаны с выделением памяти, хотя они также имеют неприятные аспекты, относящиеся к копированию. Как объясняется в совете 14, когда вы пытаетесь вставить элемент в вектор, вся память которого заполнена, вектор выделяет новый блок памяти, копирует элементы из старой памяти в новую, уничтожает элементы в старой памяти и освобождает ее.</p>
<p>После этого вставляется новый элемент. В совете 14 также говорится о том, что при заполнении всей памяти многие реализации векторов удваивают свою емкость, поэтому вставка <code>numValues</code> новых элементов может привести к тому, что новая память будет выделяться со временем log<sub>2</sub>numValues. В совете 14 упоминается о существовании реализации, обладающей таким поведением, поэтому последовательная вставка 1000 элементов может привести к 10 операциям выделения памяти с побочными затратами на копирование элементов). С другой стороны, интервальная вставка может вычислить объем необходимой памяти еще до начала вставки (если ей передаются прямые итераторы), поэтому ей не придется выделять новую память больше одного раза. Как нетрудно предположить, экономия может оказаться довольно существенной.</p>
<p>Приведенные рассуждения относились к векторам, но они в равной степени применимы и к строкам. В определенной степени они относятся и к декам, но по механизму управления памятью деки отличаются от векторов и строк, поэтому аргумент относительно многократного выделения памяти в этом случае не действует. Впрочем, два других фактора (лишние перемещения элементов в памяти и лишние вызовы функций) обычно все же действуют, хотя и несколько иным образом.</p>
<p>Из стандартных последовательных контейнеров остается только <code>list</code>, но и в этом случае интервальная форма <code>insert</code> обладает преимуществами перед одноэлементной. Конечно, такой фактор, как лишние вызовы функций, продолжает действовать, но из-за некоторых особенностей связанных списков проблемы с копированием и выделением памяти отсутствуют. Вместо них возникает другая проблема: многократные избыточные присваивания указателям <code>next</code> и <code>prev</code> для некоторых узлов списка.</p>
<p>Каждый раз, когда в связанный список включается новый элемент, необходимо присвоить значения указателям <code>next</code> и <code>prev</code> нового узла. Кроме того, необходимо задать указатель <code>next</code> предыдущего узла (назовем его узлом В) и указатель <code>prev</code> следующего узла (назовем его узлом А).</p>
<empty-line/>
<p>Предположим, в список была вставлена серия новых узлов вызовами одноэлементной версии <code>insert</code>. Во всех узлах, кроме последнего, значение <code>next</code> будет задаваться <emphasis>дважды </emphasis>— сначала указатель будет ссылаться на узел А, а затем на следующий вставленный элемент. Указатель <code>prev</code> узла А будет изменяться при каждой вставке нового узла в предшествующую позицию. Если перед А в список включаются <code>numValues</code> узлов, будет выполнено <code>numValues - 1</code> лишних присваиваний указателю <code>next</code> вставленных узлов и <code>numValues-1</code> лишних присваиваний указателю <code>prev </code>узла А, то есть в общей сложности <code>2*(numValues-l)</code> лишних операций присваивания. Конечно, присваивание указателю обходится недорого, но зачем вообще платить, если можно обойтись без этого?</p>
<p>Наверное, вы уже поняли, что без лишних присваиваний действительно можно обойтись. Для этого достаточно воспользоваться интервальной формой insert контейнера list. Функция заранее знает, сколько узлов будет вставлено в список, что позволяет сразу присвоить каждому указателю правильное значение.</p>
<p>Таким образом, для стандартных последовательных контейнеров выбор между одноэлементной и интервальной вставкой отнюдь не сводится к стилю программирования. Для ассоциативных контейнеров критерий эффективности уже не столь убедителен, хотя проблема лишних вызовов функций существует и в этом случае. Кроме того, некоторые специализированные разновидности интервальной вставки могут оптимизироваться и в ассоциативных контейнерах, хотя, насколько мне известно, подобные оптимизации пока существуют лишь в теории. Конечно, к тому моменту, когда вы будете читать эту книгу, теория может воплотиться на практике, и тогда интервальная вставка в ассоциативных контейнерах действительно будет превосходить одноэлементную вставку по эффективности. В любом случае она никогда не будет работать <emphasis>менее </emphasis>эффективно, поэтому вы ничего не теряете.</p>
<p>Если отвлечься от соображений эффективности, остается непреложный факт: вызовы интервальных функций более компактны, а программа становится более наглядной, что упрощает ее долгосрочное сопровождение. Даже этих двух причин вполне достаточно для того, чтобы отдать предпочтение интервальным функциям, а выигрыш в эффективности можно рассматривать как бесплатное приложение.</p>
<p>После столь пространных рассуждений о чудесах интервальных функций было бы уместно привести краткую сводку таких функций. Если вы заранее знаете, какие функции контейнеров существуют в интервальных версиях, вам будет проще определить, когда ими можно воспользоваться. В приведенных ниже сигнатурах тип iterator в действительности означает тип итератора для данного контейнера, то есть <emphasis>контейнер::</emphasis>iterator. С другой стороны, тип InputIterator означает любой допустимый итератор ввода.</p>
<p>•<strong>Интервальные конструкторы</strong>. У всех стандартных контейнеров существуют конструкторы следующего вида:</p>
<p><code><emphasis>контейнер::контейнер( </emphasis>InputIterator begin, // Начало интервала 	InputIterator end);	// Конец интервала</code></p>
<p>При передаче этому конструктору итераторов istream_iterator и isreambuf_ iterator (совет 29) иногда встречается одна из самых удивительных ошибок С++, вследствие которой компилятор интерпретирует эту конструкцию как объявление функции, а не как определение нового объекта контейнера. В совете 6 рассказано все, что необходимо знать об этой ошибке, в том числе и способы ее преодоления.</p>
<p>•<strong>Интервальная вставка</strong>. Во всех стандартных последовательных контейнерах присутствует следующая форма insert:</p>
<p><code>void <emphasis>контейнер:</emphasis>:insert(iterator position. // Позиция вставки</code></p>
<p><code>	InputIterator  begin, // Начало интервала</code></p>
<p><code>	InputIterator end); // Конец интервала</code></p>
<p>Ассоциативные контейнеры определяют позицию вставки при помощи собственных функций сравнения, поэтому в них предусмотрена сигнатура без параметра position:</p>
<p><code>void <emphasis>контейнер:</emphasis>:insert(InputIterator begin, InputIterator end);</code></p>
<p>Рассматривая возможности замены одноэлементных вызовов insert интервальными версиями, не забывайте, что некоторые одноэлементные варианты маскируются под другими именами. Например, push_front и push_back заносят в контейнер отдельный элемент, хотя в их названии отсутствует слово insert. Если в программе встречается циклический вызов push_front/push_back или алгоритм (например, сору), которому в качестве параметра передается front_inserter или back_inserter, перед вами потенциальный кандидат для применения интервальной формы insert.</p>
<p>•Интервальное удаление. Интервальная форма erase существует в каждом стандартном контейнере, но типы возвращаемого значения отличаются для последовательных и ассоциативных контейнеров. В последовательных контейнерах используется следующий вариант сигнатуры:</p>
<p><code>iterator <emphasis>контейнер:</emphasis>:erase(iterator begin, iterator end);</code></p>
<p>В ассоциативных контейнерах сигнатура выглядит так:</p>
<p><code>void <emphasis>контейнер:</emphasis>:erase(iterator begin, iterator end);</code></p>
<p>Чем обусловлены различия? Утверждается, что в ассоциативных контейнерах возврат итератора (для элемента, следующего за удаленным) привел бы к неприемлемому снижению быстродействия. Мне и многим другим это утверждение кажется сомнительным, но Стандарт есть Стандарт, а в нем сказано, что версии erase для последовательных и ассоциативных контейнеров обладают разными типами возвращаемого значения.</p>
<p>Многое из того, что говорилось в этом совете по поводу эффективности inser<emphasis>t, </emphasis>относится и к erase. Интервальная форма erase также сокращает количество вызовов функций по сравнению с одноэлементной формой. При одноэлементном удалении элементы тоже сдвигаются на одну позицию к своему итоговой позиции, тогда как в интервальном варианте каждый элемент перемещается к итоговой позиции за одну операцию.</p>
<p>Но erase не присущ такой недостаток insert контейнеров vector и string, как многократные выделения памяти (конечно, для erase речь пойдет о многократном <emphasis>освобождении). </emphasis>Дело в том, что память, занимаемая vector и string, автоматически увеличивается для новых элементов, но при уменьшении количества элементов память не освобождается (в совете 17 рассказано о том, как уменьшить затраты освободившейся памяти в vector и string).</p>
<p>К числу особенно важных аспектов интервального удаления относится идиома erase-remove, описанная в совете 29.</p>
<p>•Интервальное присваивание. Как упоминалось в самом начале совета, во всех последовательных контейнерах предусмотрена интервальная форма assign:</p>
<p><code>void <emphasis>контейнер:</emphasis>:assign(InputIterator begin, InputIterator end);</code></p>
<p>Итак, мы рассмотрели три веских аргумента в пользу применения интервальных функций вместо их одноэлементных аналогов. Интервальные функции обеспечивают более простую запись, они более четко выражают ваши намерения и обладают более высоким быстродействием. Против этого трудно что-либо возразить.</p>
</section>
<section>
<title>
<p>Совет 6. Остерегайтесь странностей лексического разбора С++</p>
</title>
<p>Предположим, у вас имеется файл, в который записаны числа типа int, и вы хотите скопировать эти числа в контейнер list. На первый взгляд следующее решение выглядит вполне разумно:</p>
<p><code>ifstream dataFile("ints.dat");</code></p>
<p><code>list&lt;int&gt; data(istream_iterator&lt;int&gt;(dataFile), // Внимание! Эта строка</code></p>
<p><code>	istream_iterator&lt;int&gt;()); // работает не так, как</code></p>
<p><code>	// вы предполагали</code></p>
<p>Идея проста: передать пару istream_iterator интервальному конструктору list (совет 5), после чего скопировать числа из файла в список.</p>
<p>Программа будет компилироваться, но во время выполнения она ничего не сделает. Она не прочитает данные из файла. Она даже не создаст список — а все потому, что вторая команда не объявляет список и не вызывает конструктор. Вместо этого она... Произойдет нечто настолько странное, что я даже не рискну прямо сказать об этом, потому что вы мне не поверите. Вместо этого я попробую объяснить суть дела постепенно, шаг за шагом. Надеюсь, вы сидите? Если нет — лучше поищите стул...</p>
<p>Начнем с азов. Следующая команда объявляет функцию f, которая получает double и возвращает int:</p>
<p><code>int f(double d);</code></p>
<p>То же самое происходит и в следующей строке. Круглые скобки вокруг имени параметра d не нужны, поэтому компилятор их игнорирует:</p>
<p><code>int f(double(d));// То же,- круглые скобки вокруг d игнорируются</code></p>
<p>Рассмотрим третий вариант объявления той же функции. В нем просто не указано имя параметра:</p>
<p><code>int f(double);// То же; имя параметра не указано</code></p>
<p>Вероятно, эти три формы объявления вам знакомы, хотя о возможности заключать имена параметров в скобки известно далеко не всем (до недавнего времени я о ней не знал).</p>
<p>Теперь рассмотрим еще три объявления функции. В первом объявляется функция g с параметром — указателем на функцию, которая вызывается без параметров и возвращает double:</p>
<p><code>int g(double (*pf)()); // Функции g передается указатель на функцию</code></p>
<p>То же самое можно сформулировать и иначе. Единственное различие заключается в том, что <code>pf</code> объявляется в синтаксисе без указателей (допустимом как в С, так и в С++):</p>
<p><code>int g(double pf()); // То же; pf неявно интерпретируется как указатель</code></p>
<p>Как обычно, имена параметров могут опускаться, поэтому возможен и третий вариант объявления <strong>g </strong>без указания имени <strong>pf:</strong></p>
<p><code>int g(double());// То же: имя параметра не указано</code></p>
<p>Обратите внимание на различия между круглыми скобками <emphasis>вокруг имени параметра </emphasis>(например, параметра <strong>d </strong>во втором объявлении <strong>f</strong>) и <emphasis>стоящими отдельно </emphasis>(как в этом примере). Круглые скобки, в которые заключено имя параметра, игнорируются, а круглые скобки, стоящие отдельно, обозначают присутствие списка параметров; они сообщают о присутствии параметра, который является указателем на функцию.</p>
<p>После небольшой разминки с объявлениями <strong>f </strong>и g мы возвращаемся к фрагменту, с которого начинается этот совет. Ниже он приводится снова:</p>
<p><code>list&lt;int&gt; data(istream_iterator&lt;int&gt;(dataFile),</code></p>
<p><code>istream_iterator&lt;int&gt;());</code></p>
<p>Держитесь и постарайтесь не упасть. Перед вами объявление <emphasis>функции </emphasis><code>data</code>, возвращающей тип list&lt;int&gt;. Функция <code>data</code> получает два параметра:</p>
<p>•Первый параметр, <strong>dataFile, </strong>относится к типу <strong>istream_iterator&lt;int&gt;. </strong>Лишние круглые скобки вокруг <strong>dataFile </strong>игнорируются.</p>
<p>•Второй параметр не имеет имени. Он относится к типу указателя на функцию, которая вызывается без параметров и возвращает <strong>istream_iterator&lt;int&gt;</strong><emphasis>.</emphasis></p>
<p>Любопытно, не правда ли? Однако такая интерпретация соответствует одному из основных правил С++: все, что может интерпретироваться как указатель на функцию, должно интерпретироваться именно так. Каждый программист с опытом работы на С++ встречался с теми или иными воплощениями этого правила. Сколько раз вы встречались с такой ошибкой:</p>
<p><code>class Widget{...};// Предполагается, что у Widget</code></p>
<p><code>// имеется конструктор по умолчанию </code></p>
<p><code>Widget w();// Какая неприятность...</code></p>
<p>Вместо объекта класса Widget с именем w в этом фрагменте объявляется функция w, которая вызывается без параметров и возвращает Widget. Умение распознавать подобные «ляпы» — признак хорошей квалификации программиста С++.</p>
<p>Все это по-своему интересно, однако мы нисколько не приблизились к поставленной цели: инициализировать объект list<strong>&lt;int&gt; </strong>содержимым файла. Зато теперь мы знаем, в чем заключается суть проблемы, и легко справимся с ней. Объявления формальных параметров не могут заключаться в круглые скобки, но никто не запрещает заключить в круглые скобки аргумент при вызове функции, поэтому простое добавление круглых скобок поможет компилятору увидеть происходящее под нужным углом зрения:</p>
<p><code>list&lt;int&gt; data((istream_iterator&lt;int&gt;(dataFile)), // Обратите внимание 	istream_iterator&lt;int&gt;());	// на круглые скобки</code></p>
<p><code>		// вокруг первого аргумента</code></p>
<p><code>		// конструктора list</code></p>
<p>Именно так следует объявлять данные. Учитывая практическую полезность istream_iterator и интервальных конструкторов (совет 5), этот прием стоит запомнить.</p>
<p>К сожалению, не все компиляторы знают об этом. Из нескольких протестированных компиляторов почти половина соглашалась только на <emphasis>неправильное </emphasis>объявление data без дополнительных круглых скобок! Чтобы умиротворить такие компиляторы, можно закатить глаза и воспользоваться неверным, как было показано выше, объявлением <strong>data, </strong>но это недальновидное и плохо переносимое решение.</p>
<p>Более грамотный выход заключается в том, чтобы отказаться от модного использования анонимных объектов <strong>istream_iterator </strong>при объявлении <strong>data </strong>и просто присвоить этим итераторам имена. Следующий фрагмент работает всегда:</p>
<p><code>ifstream dataFile("ints.dat");</code></p>
<p><code>istream_iterator&lt;int&gt; dataBegin(dataFile); </code></p>
<p><code>istream_iterator&lt;int&gt; dataEnd;</code></p>
<p><code>list&lt;int&gt; data(dataBegin.dataEnd);</code></p>
<p>Именованные объекты итераторов противоречат стандартному стилю программирования STL, но зато ваша программа будет однозначно восприниматься как компиляторами, так и людьми, которые с ними работают.</p>
</section>
<section>
<title>
<p>Совет 7. При использовании контейнеров указателей, для которых вызывался оператор new, не забудьте вызвать delete для указателей перед уничтожением контейнера</p>
</title>
<p>Контейнеры STL отличаются умом и сообразительностью. Они поддерживают итераторы для перебора как в прямом, так и в обратном направлении <strong>(begin, end, rbegin </strong>и т. д.); они могут сообщить тип хранящихся в них объектов <strong>(value</strong>_t<strong>ype); </strong>они выполняют все необходимые операции управления памятью при вставке и удалении; они сообщают текущее количество элементов и максимальную вместимость <strong>(size </strong>и <strong>max_size </strong>соответственно); и, конечно же, они автоматически уничтожают все хранящиеся в них объекты при уничтожении самого контейнера.</p>
<p>Работая с такими интеллектуальными контейнерами, многие программисты вообще забывают о необходимости «прибрать за собой» и надеются, что контейнер выполнит за них всю грязную работу. Нередко их ожидания оправдываются, но если контейнер содержит <emphasis>указатели </emphasis>на объекты, созданные оператором <strong>new, </strong>этого не происходит. Разумеется, контейнер указателей уничтожает все хранящиеся в нем элементы при уничтожении самого контейнера, но «деструктор» указателя ничего не делает! Он не вызывает <strong>delete.</strong></p>
<p>В результате при выполнении следующего фрагмента возникает утечка ресурсов:</p>
<p><code>void doSomething() {</code></p>
<p><code>vector&lt;Widget*&gt; vwp;</code></p>
<p><code>for (int i=0;i&lt;SOME_MAGIC_NUMBER;++i) vwp.push_back(new Widget);</code></p>
<p><code>	// Использовать vwp </code></p>
<p><code>}	// Здесь происходит утечка Widget!</code></p>
<p>Все элементы vwp уничтожаются при выходе vwp из области видимости, но это не изменяет того факта, что delete не вызывается для объектов, созданных оператором new. За удаление таких элементов отвечает программист, а не контейнер. Так было задумано. Только программист знает, <emphasis>нужно ли </emphasis>вызывать delete для этих указателей.</p>
<p>Обычно это делать нужно. На первый взгляд решение выглядит довольно просто:</p>
<p><code>void doSomethng() {</code></p>
<p><code>vector&lt;Widget*&gt; vwp;</code></p>
<p><code>... // Как прежде </code></p>
<p><code>for (vector&lt;Widget*&gt;::iterator =vwp.begin();</code></p>
<p><code>   i != vwp.end();</code></p>
<p><code>   ++i)</code></p>
<p><code>   delete *i;</code></p>
<p><code>}</code></p>
<p>Такое решение работает, если не проявлять особой разборчивости в трактовке этого понятия. Во-первых, новый цикл for делает примерно то же, что и foreach, но он не столь нагляден (совет 43). Во-вторых, этот код небезопасен по отношению к исключениям. Если между заполнением vwp указателями и вызовом delete произойдет исключение, это снова приведет к утечке ресурсов. К счастью, с обеими проблемами можно справиться.</p>
<p>Чтобы от foreach-подобного цикла перейти непосредственно к foreach, необходимо преобразовать delete в объект функции. С этим справится даже ребенок — если, конечно, вы найдете ребенка, который захочет возиться с STL:</p>
<p><code>template &lt;typename Т&gt;</code></p>
<p><code>struct DeleteObject:// В совете 40 показано,</code></p>
<p><code>   public unary_function&lt;const T*.void&gt; { // зачем нужно наследование</code></p>
<p><code>   void operator()(const Т* ptr) const </code></p>
<p><code>   {</code></p>
<p><code>      delete ptr;</code></p>
<p><code>   }</code></p>
<p><code>};</code></p>
<p>Теперь становится возможным следующее:</p>
<p><code>void doSomething() {</code></p>
<p><code>//См. ранее</code></p>
<p><code>for_each(vwp.begin(),vwp.end(),DeleteObject&lt;Widget&gt;());</code></p>
<p><code>}</code></p>
<p>К сожалению, вам приходится указывать тип объектов, удаляемых DeleteObject (в данном примере Widget), а это раздражает, vwp представляет собой vector&lt;Widget*&gt; — <emphasis>разумеется, </emphasis>DeleteObject будет удалять указатели Widget*! Подобные излишества не только раздражают, но и приводят к возникновению трудно обнаружимых ошибок. Допустим, кто-нибудь по случайности объявляет класс, производный от string:</p>
<p><code>class SpecialString: public string{...};</code></p>
<p>Это рискованно, поскольку string, как и все стандартные контейнеры STL, не имеет виртуального деструктора, а открытое наследование от классов без виртуального деструктора относится к числу основных табу С++. Подробности можно найти в любой хорошей книге по С++. (В «Effective С++» ищите в совете 14.) И все же некоторые программисты поступают подобным образом, поэтому давайте разберемся, как будет вести себя следующий код:</p>
<p><code>void doSomething() {</code></p>
<p><code>deque&lt;SpecialString*&gt; dssp:</code></p>
<p><code>for_each(dssp.begin(),end(),// Непредсказуемое поведение! Удаление</code></p>
<p><code>DeleteObject&lt;string&gt;()); // производного объекта через указатель</code></p>
<p><code>// на базовый класс при отсутствии // виртуального деструктора</code></p>
<p><code>}</code></p>
<p>Обратите внимание: dssp объявляется как контейнер, в котором хранятся указатели SpecialString*, но автор цикла for_each сообщает DeleteObject, что он будет удалять указатели string*. Понятно, откуда берутся подобные ошибки. По своему поведению SpecialString имеет много общего со string, поэтому клиенту легко забыть, что вместо string он использует SpecialString.</p>
<p>Чтобы устранить ошибку (а также сократить объем работы для клиентов DeleteObject), можно предоставить компилятору возможность вычислить тип указания, передаваемого DeleteObject::operator(). Все, что для этого нужно, — переместить определение шаблона из DeleteObject в operator():</p>
<p><code>struct DeleteObject{// Убрали определение шаблона</code></p>
<p><code>   // и базовый класс</code></p>
<p><code>   template&lt;typename Т&gt;// Определение шаблона</code></p>
<p><code>   void operator()(const Т* ptr) const</code></p>
<p><code>   {</code></p>
<p><code>      delete ptr;</code></p>
<p><code>   }</code></p>
<p><code>};</code></p>
<p>Компилятор знает тип указателя, передаваемого <strong>DeleteObject:: operator()</strong>, поэтому мы можем заставить его автоматически создать экземпляр <strong>operator() </strong>для этого типа указателя. Недостаток подобного способа вычисления типа заключается в том, что мы отказываемся от возможности сделать объект <strong>DeleteObject </strong>адаптируемым (совет 40). Впрочем, если учесть, на какое применение он рассчитан, вряд ли это можно считать серьезным недостатком.</p>
<p>С новой версией <strong>DeleteObject</strong> код клиентов <strong>SpecialString </strong>выглядит так:</p>
<p><code>void doSomething()</code></p>
<p><code>{</code></p>
<p><code>   deque&lt;SpecialString*&gt; dssp;</code></p>
<p><code>...</code></p>
<p><code>   for_each(dssp.begin(),dssp.end(),</code></p>
<p><code>      DeleteObject());// Четко определенное поведение</code></p>
<p><code>}</code></p>
<p>Такое решение прямолинейно и безопасно по отношению к типам, что и требовалось.</p>
<p>Однако безопасность исключений все еще не достигнута. Если исключение произойдет после создания <strong>SpecialString </strong>оператором <strong>new, </strong>но перед вызовом <strong>foreach, </strong>снова произойдет утечка ресурсов. Проблема решается разными способами, но простейший выход заключается в переходе от контейнера указателей к контейнеру <emphasis>умных указателей </emphasis>(обычно это указатели с подсчетом ссылок). Если вы незнакомы с концепцией умных указателей, обратитесь к любой книге по С++ для программистов среднего уровня и опытных. В книге «More Effective С++» этот материал приводится в совете 28.</p>
<p>Библиотека STL не содержит умных указателей с подсчетом ссылок. Написание хорошего умного указателя (то есть такого, который бы всегда правильно работал) — задача не из простых, и заниматься ею стоит лишь в случае крайней необходимости. Я привел код умного указателя с подсчетом ссылок в «More Effective С++» в 1996 году. Хотя код был основан на хорошо известной реализации умного указателя, а перед изданием книги материал тщательно проверялся опытными программистами, за эти годы было найдено несколько ошибок. Количество нетривиальных сбоев, возникающих при подсчете ссылок в умных указателях, просто невероятно (за подробностями обращайтесь к списку опечаток и исправлений для книги «More Effective С++» [28]).</p>
<p>К счастью, вам вряд ли придется создавать собственные умные указатели, поскольку найти проверенную реализацию не так сложно. Примером служит указатель shared_ptr из библиотеки Boost (совет 50). Используя shared_ptr, можно записать исходный пример данного совета в следующем виде:</p>
<p><code>void doSomething() {</code></p>
<p><code>typedef boost::shared_ptr&lt;Widget&gt; SPW; //SPW = "shared pointer</code></p>
<p><code>	// to Widget"</code></p>
<p><code>vector&lt;SPW&gt; vwp;</code></p>
<p><code>for (int i=0;i&lt;SOME_MAGIC_NUMBER;++i) //Создать SPW no Widget*</code></p>
<p><code>vwp.push_back(SPW(new Widget));//и вызвать push_back</code></p>
<p><code>//Использовать vwp</code></p>
<p><code>}//Утечки Widget не происходит.</code></p>
<p><code>//даже если в предыдущем фрагменте</code></p>
<p><code>//произойдет исключение</code></p>
<p><emphasis>Никогда </emphasis>не следует полагать, что автоматическое удаление указателей можно обеспечить созданием контейнера, содержащего auto_ptr. Эта кошмарная мысль чревата такими неприятностями, что я посвятил ей совет 8.</p>
<p>Главное, что необходимо запомнить: контейнеры STL разумны, но они не смогут решить, нужно ли удалять хранящиеся в них указатели. Чтобы избежать утечки ресурсов при работе с контейнерами указателей, необходимо либо воспользоваться объектами умных указателей с подсчетом ссылок (такими, как shared_ptr из библиотеки Boost), либо вручную удалить каждый указатель при уничтожении контейнера.</p>
<p>Напрашивается следующая мысль: если структура DeleteObject помогает справиться с утечкой ресурсов для контейнеров, содержащих указатели на объекты, можно создать аналогичную структуру DeleteArray, которая поможет избежать утечки ресурсов для контейнеров с указателями на массивы. Конечно, такое решение <emphasis>возможно. </emphasis>Другой вопрос, насколько оно разумно. В совете 13 показано, почему динамически размещаемые массивы почти всегда уступают vector и string, поэтому прежде чем садиться за написание DeleteArray, пожалуйста, прочитайте совет 13. Может быть, он убедит вас в том, что лучше обойтись без DeleteArray.</p>
</section>
<section>
<title>
<p>Совет 8. Никогда не создавайте контейнеры, содержащие auto_ptr</p>
</title>
<p>Честно говоря, в книге, посвященной эффективному использованию STL, данный совет не совсем уместен. Контейнеры auto_ptr (СОАР, Containers Of Auto_Ptr) запрещены, а программа, которая попытается их использовать, не будет компилироваться. Комитет по стандартизации С++ приложил неслыханные усилия в этом направлении. Возможно, мне вообще не стоило бы говорить о контейнерах auto_ ptr — о них вам расскажет компилятор, причем в самых нелестных выражениях.</p>
<p>Однако многие программисты работают на платформах STL, на которых СОАР не запрещены. Более того, многие программисты по-прежнему подвержены иллюзии и видят в СОАР простое, прямолинейное, эффективное средство для борьбы с утечкой ресурсов, часто присущей контейнерам указателей (советы 7 и 33). В результате возникает искушение воспользоваться СОАР, даже если их невозможно создать.</p>
<p>Вскоре я объясню, почему СОАР произвели такой переполох, что Комитет по стандартизации предпринял специальные шаги по их запрещению. А пока начнем с первого недостатка, для понимания которого не нужно разбираться в auto_ptr и вообще в контейнерах: СОАР не переносимы. Да и как может быть иначе? Они запрещены стандартом С++, и наиболее передовые платформы STL уже выполняют это требование. Вероятно, со временем платформы STL, которые сейчас не соответствуют Стандарту, выполнят его требования. Когда это произойдет, программы, использующие СОАР, станут еще менее переносимыми, чем сейчас. Тот, кто заботится о переносимости своих программ, отвергнет СОАР хотя бы по этой причине.</p>
<p>Впрочем, не исключено, что переносимость вас не волнует. Если это так, позвольте напомнить об уникальном (а по мнению некоторых — нелепом) смысле операции копирования auto_ptr.</p>
<p>При копировании auto_ptr право владения объектом, на который ссылается указатель, переходит к копии, а исходному указателю присваивается NULL. Да, вы не ошиблись: <emphasis>копирование указателя auto_ptr приводит к его модификации.</emphasis></p>
<p><code>auto_ptr&lt;Widget&gt; pw1(new Widget); //pw1 ссылается на Widget</code></p>
<p><code>auto_ptr&lt;Widget&gt; pw2(pw1);  //pw2 ссылается на объект Widget,</code></p>
<p><code>//принадлежащий pw1; pw1 присваивается</code></p>
<p><code>//NULL (таким образом, объект Widget</code></p>
<p><code>//передается от pw1 к pw2)</code></p>
<p><code>pwl = pw2;	//pw1 снова ссылается на Widget:</code></p>
<p><code>//pw2 присваивается NULL</code></p>
<p>Конечно, такое поведение необычно и даже по-своему интересно, но для пользователя STL в первую очередь важно то, что оно приводит к <emphasis>крайне </emphasis>неожиданным последствиям. Рассмотрим внешне безобидный фрагмент, который создает вектор auto_ptr&lt;Widget&gt; и сортирует его функцией, сравнивающей значения Widget:</p>
<p><code>bool WidgetAPCompare(const auto_ptr&lt;Widget&gt;&amp; Ihs.</code></p>
<p><code>const auto_ptr&lt;Widget&gt;&amp; rhs)</code></p>
<p><code><strong>{</strong></code></p>
<p><code>   return *lhs &lt; *rhs;// Предполагается, что для объектов Widget</code></p>
<p><code>   // существует оператор &lt;</code></p>
<p><code>}</code></p>
<p><code>vector&lt;auto_ptr&lt;Widget&gt; &gt; widgets; // Создать вектор и заполнить его</code></p>
<p><code>// указателями auto_ptr на Widget. // Помните, что этот фрагмент // не должен компилироваться!</code></p>
<p><code>sort(widgets.begin(),widgets.end(), // Отсортировать вектор</code></p>
<p><code>widgetAPCompare);</code></p>
<p>Пока все выглядит вполне разумно, да и с концептуальной точки зрения все <emphasis>действительно </emphasis>разумно — но результат разумным никак не назовешь. Например, в процессе сортировки некоторым указателям <strong>auto_ptr, </strong>хранящимся в <strong>Widget, </strong>может быть присвоено значение NULL. Сортировка вектора приводит к изменению его содержимого! Давайте разберемся, как это происходит.</p>
<p>Оказывается, реализация <strong>sort </strong>часто строится на некой разновидности алгоритма быстрой сортировки. Работа этого алгоритма строится на том, что некоторый элемент контейнера выбирается в качестве «опорного», после чего производится рекурсивная сортировка по значениям, большим и меньшим либо равным значению опорного элемента. Реализация такого алгоритма в <strong>sort </strong>может выглядеть примерно так:</p>
<p><code>template&lt;class RandomAccessIterator, // Объявление sort скопировано</code></p>
<p><code>class Compare&gt;// прямо из Стандарта</code></p>
<p><code>void sort(RandomAccessIterator first,</code></p>
<p><code>RandomAccessIterator last,</code></p>
<p><code>Compare comp)</code></p>
<p><code>{</code></p>
<p><code>	// typedef описывается ниже</code></p>
<p><code>	typedef typename iterator_traits&lt;RandomAccessIterator&gt;::value_type</code></p>
<p><code>	ElementType;</code></p>
<p><code>	RandomAccessIterator i;</code></p>
<p><code>	...// Присвоить i указатель на опорный элемент </code></p>
<p><code><strong>	ElementType pivotValue(*i</strong>); // Скопировать опорный элемент в локальную</code></p>
<p><code>	...// временную переменную; см. далее комментарий. </code></p>
<p><code>	// Остальная сортировка</code></p>
<p><code>}</code></p>
<p>Если вы не привыкли читать исходные тексты STL, этот фрагмент выглядит жутковато, но в действительности в нем нет ничего страшного. Нетривиально здесь выглядит только запись <code>iterator_traits&lt;RandomAccessIterator&gt;:: value_type,</code> но это всего лишь принятое в STL обозначение типа объекта, на который указывают итераторы, переданные sort. Перед ссылкой <code>iterator_traits&lt;RandomAccessIterator&gt;:: value_type</code> должен стоять префикс typename, поскольку это имя типа, зависящее от параметра шаблона (в данном случае <code>RandomAccessIterator</code>), — дополнительная информация приведена на с. 20.</p>
<p>Проблемы возникают из-за следующей команды, которая копирует элемент из сортируемого интервала в локальный временный объект:</p>
<p><code>ElementType pivotValue(*i);</code></p>
<p>В данном случае элементом является auto_ptr&lt;Widget&gt;, поэтому в результате скопированному указателю auto_ptr (тому, который хранится в векторе) присваивается NULL. Более того, когда pivotValue выходит из области видимости, происходит автоматическое удаление объекта Widget, на который pivotValue ссылается. Итак, после вызова sort содержимое вектора изменяется и по меньшей мере один объект Widget удаляется. Вследствие рекурсивности алгоритма быстрой сортировки существует вероятность того, что сразу нескольким элементам вектора будет присвоено значение NULL и сразу несколько объектов Widget будут удалены, поскольку опорный элемент копируется на каждом уровне рекурсии. . ^ Подобные ловушки весьма зловредны, и Комитет по стандартизации постарался, чтобы вы заведомо не попадались в них. Уважайте их труд и никогда не создавайте контейнеры auto_ptr, даже если ваша платформа STL это позволяет.</p>
<p>Впрочем, это вовсе не исключает возможности создания контейнеров умных указателей. Контейнеры умных указателей вполне допустимы. В совете 50 описано, где найти умные указатели, хорошо работающие в контейнерах STL, просто auto_ptr не относится к их числу.</p>
</section>
<section>
<title>
<p>Совет 9. Тщательно выбирайте операцию удаления</p>
</title>
<p>Допустим, у нас имеется стандартный контейнер STL с, содержащий числа типа int:</p>
<p><emphasis>контейнер&lt;int&gt; </emphasis>с;</p>
<p>и вы хотите удалить из него все объекты со значением 1963. Как ни странно, способ решения этой задачи зависит от контейнера; универсального решения не существует.</p>
<p>Для блоковых контейнеров (vector, deque или string — см. совет 1) оптимальный вариант построен на использовании идиомы erase-remove (совет 32):</p>
<p><code>c<strong>.erase(remove</strong>(c.begin().c.end(),1963). // Идиома erase-remove хорошо</code></p>
<p><code>c.end());// подходит для удаления элементов</code></p>
<p><code>// с заданным значением </code></p>
<p><code>// из контейнеров vector, string </code></p>
<p><code>//и deque</code></p>
<p>Приведенное решение работает и для контейнеров list, но как будет показано в совете 44, функция remove контейнера list работает эффективнее:</p>
<p><code>с.<strong>remove</strong>(1963); // Функция remove хорошо подходит для удаления </code></p>
<p><code>// элементов с заданным значением из списка</code></p>
<p>Стандартные ассоциативные контейнеры (такие как set, multiset, map и multimap) не имеют функции remove с именем remove, а использование алгоритма remove может привести к стиранию элементов контейнера (совет 32) и возможной порче его содержимого. За подробностями обращайтесь к совету 22, где также объясняется, почему вызовы remove для контейнеров map/multimap не компилируются никогда, а для контейнеров set/multiset — компилируются в отдельных случаях.</p>
<p>Для ассоциативных контейнеров правильным решением будет вызов erase:</p>
<p><code>c<strong>.erase</strong>(1963);// Функция erase обеспечивает оптимальное</code></p>
<p><code>// удаление элементов с заданным значением </code></p>
<p><code>// из стандартных ассоциативных контейнеров</code></p>
<p>Функция erase не только справляется с задачей, но и эффективно решает ее с логарифмической сложностью (вызовы remove в последовательных контейнерах обрабатываются с линейной сложностью). Более того, в ассоциативных контейнерах функция erase обладает дополнительным преимуществом — она основана на проверке эквивалентности вместо равенства (это важное различие рассматривается в совете 19).</p>
<p>Слегка изменим проблему. Вместо того чтобы удалять из с все объекты с заданным значением, давайте удалим все объекты, для которых следующий предикат (совет 39) возвращает true:</p>
<p><code>bool badValue(int х):// Возвращает true для удаляемых объектов</code></p>
<p>В последовательных контейнерах (vector, string, deque и list) достаточно заменить remove на remove_if:</p>
<p><code>c.erase(remove_if(c.begin(),c.end(),badValue), // Лучший способ уничтожения</code></p>
<p><code>c.end());// объектов, для которых badValue</code></p>
<p><code>// возвращает true, в контейнерах </code></p>
<p><code>// vector, string и deque</code></p>
<p><code>с.remove_if(badValue);// Оптимальный способ уничтожения</code></p>
<p><code>// объектов, для которых badValue </code></p>
<p><code>// возвращает true, в контейнере </code></p>
<p><code>// list</code></p>
<p>Со стандартными ассоциативными контейнерами дело обстоит посложнее. Существуют два решения: одно проще программируется, другое эффективнее работает. В первом решении нужные значения копируются в новый контейнер функцией remove_copy, после чего содержимое двух контейнеров меняется местами:</p>
<p><code><emphasis>АссоцКонтейнер&lt;int&gt; </emphasis>с;//с - один из стандартных</code></p>
<p><code>// ассоциативных контейнеров</code></p>
<p><code><emphasis>АссоцКонтейнер&lt;int&gt; </emphasis>goodValues: // Временный контейнер для хранения</code></p>
<p><code>// элементов, оставшихся после удаления</code></p>
<p><code><strong>remove_copy</strong>_i<strong>f</strong>(c.begin().c.end(), // Скопировать оставшиеся элементы inserter(goodValues, // из с в goodValues</code></p>
<p><code>goodValues.end()), badValue);</code></p>
<p><code>с.<strong>swap</strong>(goodValues);// Поменять содержимое с и goodValues</code></p>
<p>У подобного решения имеется недостаток — необходимость копирования элементов, остающихся после удаления. Такое копирование может обойтись дороже, чем нам хотелось бы.</p>
<p>От этих затрат можно избавиться за счет непосредственного удаления элементов из исходного контейнера. Но поскольку в ассоциативных контейнерах отсутствует функция, аналогичная remove_if, придется перебирать все элементы с в цикле и принимать решение об удалении текущего элемента.</p>
<p>С концептуальной точки зрения эта задача несложна, да и реализуется она просто. К сожалению, решение, которое первым приходит в голову, редко бывает правильным. Вероятно, многие программисты предложат следующий вариант:</p>
<p><code><emphasis>АссоцКонтейнер&lt;int&gt; </emphasis>с;</code></p>
<p><code><emphasis>for(АссоцКонтейнер&lt;int&gt;::</emphasis>iterator<emphasis> i</emphasis>=cbegin(); // Наглядный, бесхитростный </code></p>
<p><code>	i!=cend();// и <emphasis>ошибочный </emphasis>код, который</code></p>
<p><code>	++i) {// стирает все элементы с</code></p>
<p><code>	if (badValue(*i)) c.erase<emphasis>(i):</emphasis>// для которых badValue</code></p>
<p><code>}// возвращает true.</code></p>
<p><code>// Не <emphasis>поступайте так!</emphasis></code></p>
<p>Выполнение этого фрагмента приводит к непредсказуемым результатам. При стирании элемента контейнера все итераторы, указывающие на этот элемент, становятся недействительными. Таким образом, после возврата из c.erase(i) итератор i становится недействительным. Для нашего цикла это фатально, поскольку после вызова erase итератор i увеличивается (++i в заголовке цикла fo<emphasis>r).</emphasis></p>
<p>Проблема решается просто: необходимо позаботиться о том, чтобы итератор переводился на следующий элемент с перед вызовом erase. Это проще всего сделать постфиксным увеличением i при вызове:</p>
<p><code><emphasis>АссоцКонтейнер&lt;int&gt; </emphasis>с;</code></p>
<p><code><emphasis>for(АссоцКонтейнер&lt;int&gt;::</emphasis>iterator i=c.begin();// Третья часть заголовка</code></p>
<p><code>	i!=c.end();	// цикла for пуста; i теперь</code></p>
<p><code>	/* пусто */) {// изменяется внутри цикла</code></p>
<p><code>	if (badValue(*i)) c.erase(i++);// Для удаляемых элементов</code></p>
<p><code>	else ++i;// передать erase текущее</code></p>
<p><code>}// значение i и увеличить i.</code></p>
<p><code>// Для остающихся элементов // просто увеличить i</code></p>
<p>Новый вариант вызова erase работает, поскольку выражение i++ равно старому значению i, но у него имеется побочный эффект — приращение i. Таким образом, мы передаем старое (не увеличенное) значение i и увеличиваем i перед вызовом erase. Именно это нам и требовалось. Хотя это решение выглядит просто, лишь немногие программисты предложат его с первой попытки.</p>
<p>Пора сделать следующий шаг. Помимо простого удаления всех элементов, для которых badValue возвращает true, мы также хотим регистрировать каждую операцию удаления в журнале.</p>
<p>Для ассоциативных контейнеров задача решается очень просто, поскольку она требует лишь тривиальной модификации созданного цикла:</p>
<p><code>ofstream logFile;// Файл журнала <emphasis>АссоцКонтейнер&lt;int&gt; </emphasis>с;</code></p>
<p><code><emphasis>for{АссоцКонтейнер&lt;int&gt;: </emphasis>iterator i=c.begin();// Заголовок цикла остается</code></p>
<p><code>i!=c.end();) {// без изменений</code></p>
<p><code>if (badValue(*i)) {</code></p>
<p><code><strong>	logFile«"Erasing "« *i «'\n';	</strong>// Вывод в журнал</code></p>
<p><code>	c.erase(i++):// Удаление</code></p>
<p><code><strong>}</strong></code></p>
<p><code>else ++i:</code></p>
<p><code>}</code></p>
<p>На этот раз хлопоты возникают с vecto<emphasis>r, </emphasis>string и deque. Использовать идиому erase/remove не удается, поскольку erase или remove_if нельзя заставить вывести данные в журнал. Более того, вариант с циклом fo<emphasis>r, </emphasis>только что продемонстрированный для ассоциативных контейнеров, тоже не подходит, поскольку для контейнеров vecto<emphasis>r, </emphasis>string и deque он приведет к непредсказуемым последствиям. Вспомните, что для этих контейнеров в результате вызова erase становятся недействительными все итераторы, указывающие на удаляемый элемент. Кроме того, недействительными становятся все итераторы <emphasis>после </emphasis>удаляемого элемента, в нашем примере — все итераторы после i. Конструкции вида i++, ++i и т. д. невозможны, поскольку ни один из полученных итераторов не будет действительным.</p>
<p>Следовательно, с vecto<emphasis>r, </emphasis>string и deque нужно действовать иначе. Мы должны воспользоваться возвращаемым значением erase, которое содержит именно то, что нам требуется — действительный итератор, который указывает на элемент, следующий за удаленным. Иначе говоря, программа выглядит примерно так:</p>
<p><code>for <emphasis>(ПослКонтейнер&lt;int&gt;:</emphasis>:iterator=cbegin(); i !=cend();){</code></p>
<p><code>if (badValue(*i)) {</code></p>
<p><code>	logFile«"Erasing "«*i«'\n';</code></p>
<p><code>	i=c.erase());// Сохраняем действительный итератор i,</code></p>
<p><code>}// для чего ему присваивается значение,</code></p>
<p><code>else ++i;// возвращаемое функцией erase</code></p>
<p><code>}</code></p>
<p>Такое решение превосходно работает, но только для стандартных последовательных контейнеров. По весьма сомнительным причинам (совет 5) функция erase для стандартных ассоциативных контейнеров возвращает void. В этом случае приходится использовать методику с постфиксным приращением итератора, переданного erase. Кстати говоря, подобные различия между последовательными и ассоциативными контейнерами — один из примеров того, почему контейнерно-независимый код обычно считается нежелательным (совет 2).</p>
<p>Какое из этих решений лучше подойдет для контейнера list? Оказывается, в отношении перебора и удаления list может интерпретироваться как vector<emphasis>/ </emphasis>string/deque или как ассоциативный контейнер — годятся оба способа. Обычно выбирается первый вариант, поскольку list, как и vector/string/deque, принадлежит к числу последовательных контейнеров. С точки зрения опытного программиста STL программа, в которой перебор и удаление из list производятся по правилам ассоциативных контейнеров, выглядит странно.</p>
<p>Подводя итог всему, о чем рассказывалось в этом совете, мы приходим к следующим заключениям.</p>
<p>Удаление всех объектов с заданным значением:</p>
<p>•контейнеры vector, string и deque: используйте идиому erase/remove;</p>
<p>•контейнер list: используйте list::remove;</p>
<p>•стандартный ассоциативный контейнер: используйте функцию erase.</p>
<p>Удаление всех объектов, соответствующих заданному предикату:</p>
<p>•контейнер vector, string и deque: используйте идиому erase/remove_if;</p>
<p>•контейнер list: используйте list:: remove_if;</p>
<p>•стандартный ассоциативный контейнер: используйте remove_copy_if/swap или напишите цикл перебора элементов контейнера, но не забудьте о постфиксном приращении итератора, передаваемого при вызове erase.</p>
<p>Дополнительные операции в цикле (кроме удаления объектов):</p>
<p>•стандартный последовательный контейнер: напишите цикл перебора элементов, но не забывайте обновлять итератор значением, возвращаемым erase при каждом вызове;</p>
<p>•стандартный ассоциативный контейнер: напишите цикл перебора элементов с постфиксным приращением итератора, передаваемого при вызове erase.</p>
<p>Как видите, эффективное удаление элементов контейнера не сводится к простому вызову erase. Правильный подход зависит от того, по какому принципу отбираются удаляемые элементы, в каком контейнере они хранятся и какие дополнительные операции требуется выполнить при удалении. Действуйте осторожно и следуйте рекомендациям данного совета, и все будет нормально. Невнимательность обернется неэффективной работой или непредсказуемым поведением программы.</p>
</section>
<section>
<title>
<p>Совет 10. Помните о правилах и ограничениях распределителей памяти</p>
</title>
<p>Распределители памяти первоначально разрабатывались как абстракция для моделей памяти, позволяющих разработчикам библиотек игнорировать различия между near- и far-указателями в некоторых 16-разрядных операционных системах (например, DOS и ее зловредных потомках), однако эта попытка провалилась. Распределители также должны были упростить разработку объектных диспетчеров памяти, но вскоре выяснилось, что такой подход снижает эффективность работы некоторых компонентов STL. Чтобы избежать снижения быстродействия. Комитет по стандартизации С++ включил в Стандарт положение, которое практически выхолостило объектные распределители памяти, но одновременно выражало надежду, что от этой операции их потенциальные возможности не пострадают.</p>
<p>Но это еще не все. Распределители памяти STL, как и operator new с operator new[ ], отвечают за выделение (и освобождение) физической памяти, однако их клиентский интерфейс имеет мало общего с клиентским интерфейсом operator new, operator new[ ] и даже malloc. Наконец, большинство стандартных контейнеров никогда не запрашивает память у своих распределителей. Еще раз подчеркиваю — <emphasis>никогда. </emphasis>В результате распределители производят довольно странное впечатление.</p>
<p>Впрочем, это не их вина, и, конечно же, из этого факта вовсе не следует делать вывод о бесполезности распределителей. Тем не менее, прежде чем описывать области применения распределителей (эта тема рассматривается в совете 11), я должен объяснить, для чего они не подходят. Существует целый ряд задач, которые только <emphasis>на первый взгляд </emphasis>могут решаться при помощи распределителей. Прежде чем вступать в игру, желательно изучить границы игрового поля, в противном случае вы наверняка упадете и получите травму. Кроме того, из-за экзотических особенностей распределителей сам процесс обобщения выглядит весьма поучительным и занимательным. По крайней мере, я на это надеюсь.</p>
<p>Перечень особенностей распределителей начинается с рудиментарных определений типов для указателей и ссылок. Как упоминалось выше, распределители изначально были задуманы как абстракции для моделей памяти, поэтому казалось вполне логичным возложить на них обеспечение определения типов (typedef) для указателей и ссылок в определяемой модели. В стандарте С++ стандартный распределитель объектов типа Т (allocator&lt;T&gt;) предоставляет определения allocator&lt;T&gt;:: pointer и allocator&lt;T&gt;:: reference, поэтому предполагается, что пользовательские распределители также будут предоставлять эти определения.</p>
<p>Ветераны С++ немедленно почуют неладное, поскольку в С++ не существует средств для имитации ссылок. Для этого пришлось бы перегрузить operator. (оператор «точка»), а это запрещено. Кроме того, объекты, работающие как ссылки, являются примером <emphasis>промежуточных объектов </emphasis>(proxy objects), а использование промежуточных объектов приводит к целому ряду проблем, одна из которых описана в совете 18. Подробное описание промежуточных объектов приведено в совете 30 книги «More Effective С++».</p>
<p>В случае распределителей STL бессмысленность определений типов для указателей и ссылок объясняется не техническими недостатками промежуточных объектов, а следующим фактом: Стандарт разрешает считать, что определение типа pointer любого распределителя является синонимом Т* а определение типа reference — синонимом Т&amp;. Да, все верно, разработчики библиотек могут игнорировать определения и использовать указатели и ссылки напрямую! Таким образом, даже если вам удастся написать распределитель с новыми определениями для указателей и ссылок, никакой пользы от этого не будет, поскольку используемая реализация STL запросто сможет эти определения проигнорировать. Интересно, не правда ли?</p>
<p>Пока вы не успели осмыслить этот пример странностей стандартизации, я приведу следующий. Распределители являются объектами, из чего следует, что они могут обладать собственными функциями, вложенными типами и определениями типов (такими как pointer и reference). Однако в соответствии со Стандартом реализация STL может предполагать, что все однотипные объекты распределителей эквивалентны и почти всегда равны. Разумеется, это обстоятельство объяснялось вескими причинами. Рассмотрим следующий фрагмент:</p>
<p><code>template&lt;typename Т&gt;// Шаблон пользовательского</code></p>
<p><code>// распределителя памяти</code></p>
<p><code>class SpecialAllocator{...}</code></p>
<p><code>typedef SpecialAllocator&lt;Widget&gt; SAW; // SAW = "SpecialAllocator</code></p>
<p><code>//for Widgets"</code></p>
<p><code>list&lt;Widget.SAW&gt; LI;</code></p>
<p><code>list&lt;Widget.SAW&gt; L2;</code></p>
<p><code>Ll.splice(Ll.begin(),L2);</code></p>
<p>Вспомните: при перемещении элементов из одного контейнера <strong>list </strong>в другой функцией splice данные не копируются. Стоит изменить значения нескольких указателей, и узлы, которые раньше находились в одном списке, оказываются в другом, поэтому операция врезки выполняется быстро и защищена от исключений. В приведенном примере узлы, ранее находившиеся в <strong>L2, </strong>после вызова splice перемещаются в <strong>L1.</strong></p>
<p>Разумеется, при уничтожении контейнера <strong>L1 </strong>должны быть уничтожены все его узлы (с освобождением занимаемой ими памяти). А поскольку контейнер теперь содержит узлы, ранее входившие в <strong>L2, </strong>распределитель памяти <strong>L1 </strong>должен освободить память, ранее выделенную распределителем <strong>L2. </strong>Становится ясно, почему Стандарт разрешает программистам STL допускать эквивалентность однотипных распределителей. Это сделано для того, чтобы память, выделенная одним объектом-распределителем (таким как <strong>L2), </strong>могла безопасно освобождаться другим объектом-распределителем (таким как <strong>L1). </strong>Отсутствие подобного допущения привое бы к значительному усложнению реализации врезки и к снижению ее эффективности (кстати, операции врезки влияют и на другие компоненты STL, один из примеров приведен в совете 4).</p>
<p>Все это, конечно, хорошо, но чем больше размышляешь на эту тему, тем лучше понимаешь, какие жесткие ограничения накладывает предположение об эквивалентности однотипных распределителей. Из него следует, что переносимые объекты распределителей — то есть распределители памяти, правильно работающие в разных реализациях STL, — не могут обладать состоянием. Другими совами, это означает, что переносимые распределители <emphasis>не могут содержать нестатических переменных </emphasis>(по крайней мере таких, которые бы влияли на их работу). В частности, отсюда следует, что вы не сможете создать два распределителя <code>SpecialAllocator&lt;int&gt;</code>, выделяющих память из разных куч (heap). Такие распределители не были бы эквивалентными, и в некоторых реализациях STL попытки использования обоих распределителей привели бы к порче структур данных во время выполнения программы.</p>
<p>Обратите внимание: эта проблема возникает <emphasis>на стадии выполнения. </emphasis>Распределители, обладающие состоянием, компилируются вполне нормально — просто они не работают так, как предполагалось. За эквивалентностью всех однотипных распределителей вы должны следить сами. Не рассчитывайте на то, что компилятор предупредит о нарушении этого ограничения.</p>
<p>Справедливости ради стоит отметить, что сразу же за положением об эквивалентности однотипных распределителей памяти в Стандарт включен следующий текст: «<emphasis>...Авторам реализаций рекомендуется создавать библиотеки, которые... поддерживают неэквивалентные распределители. В таких реализациях... семантика контейнеров и алгоритмов для неэквивалентных экземпляров распределителей определяется самой реализацией</emphasis>».</p>
<p>Трогательное проявление заботы, однако пользователю STL, рассматривающему возможность создания нестандартного распределителя с состоянием, это не дает практически ничего. Этим положением можно воспользоваться только в том случае, если вы уверены в том, что используемая реализация STL поддерживает неэквивалентные распределители, готовы потратить время на углубленное изучение документации, чтобы узнать, подходит ли вам «определяемое самой реализацией» поведение неэквивалентных распределителей, и вас не беспокоят проблемы с переносом кода в реализации STL, в которых эта возможность может отсутствовать. Короче говоря, это положение (для особо любознательных — абзац 5 раздела 20.1.5) лишь выражает некие благие намерения по поводу будущего распределителей. До тех пор пока эти благие намерения не воплотятся в жизнь, программисты, желающие обеспечить переносимость своих программ, должны ограничиваться распределителями без состояния.</p>
<p>Выше уже говорилось о том, что распределители обладают определенным сходством с оператором new — они тоже занимаются выделением физической памяти, но имеют другой интерфейс. Чтобы убедиться в этом, достаточно рассмотреть объявления стандартных форм operator new и allocator&lt;T&gt;::allocate:</p>
<p><code>void* operator new(size_t bytes);</code></p>
<p><code>pointer allocator&lt;T&gt;::allocate(size_type numObjects);</code></p>
<p><code>// Напоминаю: pointer - определение типа.</code></p>
<p><code>//практически всегда эквивалентное Т*</code></p>
<p>В обоих случаях передается параметр, определяющий объем выделяемой памяти, но в случае с оператором new указывается конкретный объем в байтах, а в случае с <code>allocator&lt;T&gt;:: allocate</code> указывается количество объектов Т, размещаемых в памяти. Например, на платформе, где sizeof (int)=4, при выделении памяти для одного числа int оператору new передается число 4, а <code>allocator&lt;in<emphasis>t&gt;:</emphasis>:allocate</code> — число 1. Для оператора new параметр относится к типу size<emphasis>_t, </emphasis>а для функции allocate — к типу <code>allocator&lt;T&gt;::size_type</code>, В обоих случаях это целочисленная величина без знака, причем <code>allocator&lt;T&gt;::size_type</code> обычно является простым определением типа для size_<emphasis>t. </emphasis>В этом несоответствии нет ничего страшного, однако разные правила передачи параметров оператору new и allocator&lt;T&gt;:: allocate усложняют использование готовых пользовательских версий new в разработке нестандартных распределителей.</p>
<p>Оператор new отличается от allocator&lt;T&gt;:: allocate и типом возвращаемого значения. Оператор new возвращает void*, традиционный способ представления указателя на неинициализированную память в С++. Функция allocator&lt;T&gt;:: allocate возвращает T* (через определение типа pointer<emphasis>), </emphasis>что не только нетрадиционно, но и отдает мошенничеством. Указатель, возвращаемый allocator&lt;T&gt;:: allocate, не может указывать на объект Т, поскольку этот объект еще не был сконструирован! STL косвенно предполагает, что сторона, вызывающая allocator&lt;T&gt;:: allocate, сконструирует в полученной памяти один или несколько объектов Т (вероятно, посредством allocator&lt;T&gt;:: construc<emphasis>t, </emphasis>uniniialized_fill или raw_storage_iterator), хотя в случае vector<emphasis>::r</emphasis>eseve или string::reseve этого может никогда не произойти (совет 13). Различия в типах возвращаемых значений оператора new и allocator&lt;T&gt;:: allocate означают изменение концептуальной модели неинициализированной памяти, что также затрудняет применение опыта реализации оператора new к разработке нестандартных распределителей.</p>
<p>Мы подошли к последней странности распределителей памяти в STL: большинство стандартных контейнеров никогда не вызывает распределителей, с которыми они ассоциируются. Два примера:</p>
<p><code>list&lt;int&gt; L;// То же, что и list&lt;int,allocator&lt;int».</code></p>
<p><code>// Контейнер никогда не вызывает</code></p>
<p><code>// allocator&lt;int&gt; для выделения памяти!</code></p>
<p><code>set&lt;Widget.SAW&gt; s;// SAW представляет собой определение типа</code></p>
<p><code>// для SpeciаlAllосаtor&lt;Widget&gt;, однако </code></p>
<p><code><strong>// ни </strong>один экземпляр SAW не будет </code></p>
<p><code>// выделять память!</code></p>
<p>Данная странность присуща list и стандартным ассоциативным контейнерам <strong>(set, multiset, map </strong>и <strong>multimap). </strong>Это объясняется тем, что перечисленные контейнеры являются <emphasis>узловыми, </emphasis>то есть основаны на структурах данных, в которых каждый новый элемент размещается в динамически выделяемом отдельном узле. В контейнере list узлы соответствуют узлам списка. В стандартных ассоциативных контейнерах узлы часто соответствуют узлам дерева, поскольку стандартные ассоциативные контейнеры обычно реализуются в виде сбалансированных бинарных деревьев.</p>
<p>Давайте подумаем, как может выглядеть типичная реализация <strong>list</strong>&lt;T&gt;. Список состоит из узлов, каждый из которых содержит объект Т и два указателя (на следующий и предыдущий узлы списка).</p>
<p><code>template&lt;typename Т&gt;// Возможная реализация</code></p>
<p><code>	typename Allocator=allocator&lt;T&gt; // списка </code></p>
<p><code>class list { </code></p>
<p><code>   private:</code></p>
<p><code>Allocator alloc;// Распределитель памяти для объектов типа Т</code></p>
<p><code><strong>	struct LstNode{// Узлы связанного списка</strong></code></p>
<p><code>		Т <strong>data;</strong></code></p>
<p><code><strong>		ListNode *prev;</strong></code></p>
<p><code><strong>		ListNode *next;</strong></code></p>
<p><code>	};</code></p>
<p><code><strong>};</strong></code></p>
<p>При включении в список нового узла необходимо получить для него память от распределителя, однако нам нужна память не для Т, а для структуры <strong>ListNode, </strong>содержащей Т. Таким образом, объект <strong>Allocator </strong>становится практически бесполезным, потому что он выделяет память не для <strong>ListNode, </strong>а для Т. Теперь становится понятно, почему list никогда не обращается к <strong>allocator </strong>за памятью — последний просто не способен предоставить то, что требуется list<strong>.</strong></p>
<p>Следовательно, <strong>list </strong>нужны средства для перехода от имеющегося типа распределителя к соответствующему распределителю <strong>ListNode. </strong>Задача была бы весьма непростой, но по правилам распределитель памяти должен предоставить определение типа для решения этой задачи. Определение называется <strong>other, </strong>но не все так просто — это определение вложено в структуру с именем <strong>rebind, </strong>которая сама по себе является шаблоном, вложенным в распределитель, — причем последний тоже является шаблоном!</p>
<p>Пожалуйста, не пытайтесь вникать в смысл последней фразы. Вместо этого просто рассмотрите следующий фрагмент и переходите к дальнейшему объяснению:</p>
<p><code>template&lt;typename Т&gt;</code></p>
<p><code>class allocator {</code></p>
<p><code>public:</code></p>
<p><code>	template&lt;typename U&gt;</code></p>
<p><code>	struct rebind{</code></p>
<p><code>		typedef allocator&lt;U&gt; other;</code></p>
<p><code>	};</code></p>
<p><code>}</code></p>
<p>В программе, реализующей list&lt;T&gt;, возникает необходимость определить тип распределителя ListNode, соответствующего распределителю, существующему для Т. Тип распределителя для Т задается параметром allocator<emphasis>. </emphasis>Учитывая сказанное, тип распределителя для ListNode должен выглядеть так:</p>
<p><code>Allocator::rebind&lt;ListNode&gt;::other</code></p>
<p>А теперь будьте внимательны. Каждый шаблон распределителя А (например, <code>std::allocato<emphasis>r, </emphasis>SpecialAllocator</code> и т. д.) должен содержать вложенный шаблон структуры с именем <code>rebind</code>. Предполагается, что <code>rebind</code> получает параметр U и не определяет ничего, кроме определения типа <code>othe<emphasis>r</emphasis></code><emphasis>, </emphasis>где <code>other</code> — просто имя для А&lt;U&gt;. В результате list&lt;T&gt; может перейти от своего распределителя объектов Т (allocator<emphasis>) </emphasis>к распределителю объектов ListNode по ссылке <code>allocator<emphasis>::r</emphasis>ebind&lt;ListNode&gt;:: other.</code></p>
<p>Может, вы разобрались во всем сказанном, а может, и нет (если думать достаточно долго, вы непременно разберетесь, но подумать придется — знаю по своему опыту). Но вам как пользователю STL, желающему написать собственный распределитель памяти, в действительности не нужно точно понимать суть происходящего. Достаточно знать простой факт: если вы собираетесь создать распределитель памяти и использовать его со стандартными контейнерами, ваш распределитель должен предоставлять шаблон rebind, поскольку стандартные шаблоны будут на это рассчитывать (для целей отладки также желательно понимать, почему узловые контейнеры Т никогда не запрашивают память у распределителей объектов Т).</p>
<p>Ура! Наше знакомство со странностями распределителей памяти закончено. Позвольте подвести краткий итог того, о чем необходимо помнить при программировании собственных распределителей памяти:</p>
<p>•распределитель памяти оформляется в виде шаблона с параметром Т, представляющим тип объектов, для которых выделяется память;</p>
<p>•предоставьте определения типов pointer и reference, но следите за тем, чтобы pointer всегда был эквивалентен Т*, а reference — Т&amp;;</p>
<p>•никогда не включайте в распределители данные состояния уровня объекта. В общем случае распределитель не может содержать нестатических переменных;</p>
<p>•помните, что функциям <code>allocate</code> передается количество <emphasis><strong>объектов, </strong></emphasis>для которых необходимо выделить память, а не объем памяти в байтах. Также помните, что эти функции возвращают указатели Т* (через определение типа pointer<emphasis>) </emphasis>несмотря на то, что ни один объект Т еще не сконструирован;</p>
<p>•обязательно предоставьте вложенный шаблон rebind, от наличия которого зависит работа стандартных контейнеров.</p>
<p>Написание собственного распределителя памяти обычно сводится к копированию приличного объема стандартного кода и последующей модификации нескольких функций (в первую очередь allocate и deallocate). Вместо того чтобы писать базовый код с самого начала, я рекомендую воспользоваться кодом с web-страницы Джосаттиса [23] или из статьи Остерна «What Are Allocators Good For?» [24].</p>
<p>Материал, изложенный в этом совете, дает представление о том, чего <emphasis><strong>нe могут</strong> </emphasis>сделать распределители памяти, но вас, вероятно, больше интересует другой вопрос — что они <emphasis><strong>могут! </strong></emphasis>Это весьма обширная тема, которую я выделил в совет 11.</p>
</section>
<section>
<title>
<p>Совет 11. Учитывайте область применения пользовательских распределителей памяти</p>
</title>
<p>Итак, в результате хронометража, профилирования и всевозможных экспериментов вы пришли к выводу, что стандартный распределитель памяти STL (то есть allocator&lt;T&gt;) работает слишком медленно, напрасно расходует или фрагментирует память, и вы лучше справитесь с этой задачей. А может быть, allocator&lt;T&gt; обеспечивает безопасность в многопоточной модели, но вы планируете использовать только однопоточную модель и не желаете расходовать ресурсы на синхронизацию, которая вам не нужна. Или вы знаете, что объекты некоторых контейнеров обычно используются вместе, и хотите расположить их рядом друг с другом в специальной куче, чтобы по возможности локализовать ссылки. Или вы хотите выделить блок общей памяти и разместить в нем свои контейнеры, чтобы они могли использоваться другими процессами. Превосходно! В каждом из этих сценариев уместно воспользоваться нестандартным распределителем памяти.</p>
<p>Предположим, у вас имеются специальные функции для управления блоком общей памяти, написанные по образцу malloc и free:</p>
<p><code>void* mallocShared(size_t bytesNeeded);</code></p>
<p><code>void freeShared(void *ptr);</code></p>
<p>Требуется, чтобы память для содержимого контейнеров STL выделялась в общем блоке. Никаких проблем:</p>
<p><code>template&lt;typename Т&gt;</code></p>
<p><code>class SharedMemoryAllocator{</code></p>
<p><code>public:</code></p>
<p><code>...</code></p>
<p><code>	pointer allocate(size_type numObjects, const void* localityHint=0)</code></p>
<p><code>	{</code></p>
<p><code>	   return static_cast&lt;pointer&gt;(mal1ocShared(numObjects *szeof(T)));</code></p>
<p><code>	}</code></p>
<p><code>	void deallocate(pointer ptrToMemory, size_type numObjects) {</code></p>
<p><code>	   freeShared(ptrToMemory);</code></p>
<p><code>	}</code></p>
<p><code>}:</code></p>
<p>За информацией о типе pointer, а также о преобразовании типа и умножении при вызове allocate обращайтесь к совету 10. Пример использования SharedMemoryAllocator:</p>
<p>// Вспомогательное определение типа</p>
<p><code>typedef</code></p>
<p><code>vector&lt;double.<strong>SharedMemoryAllocator&lt;double&gt; </strong>&gt; SharedDoubleVec:</code></p>
<p><code>{// Начало блока</code></p>
<p><code>SharedDoubleVec v;// Создать вектор, элементы которого</code></p>
<p><code>// находятся в общей памяти</code></p>
<p><code>}// Конец блока</code></p>
<p>Обратите особое внимание на формулировку комментария рядом с определением <code>v</code>. Вектор <code>v</code> использует SharedMemoryAllocato<emphasis>r, </emphasis>потому память для хранения элементов <code>v</code> будет выделяться из общей памяти, однако сам вектор <code>v</code> (вместе со всеми переменными класса) почти наверняка <emphasis>не будет </emphasis>находиться в общей памяти. Вектор <code>v</code> — обычный стековый объект, поэтому он будет находиться в памяти, в которой исполнительная система хранит все обычные стековые объекты. Такая память почти никогда не является общей. Чтобы разместить в общей памяти как содержимое <code>v</code>, так и сам объект <code>v</code>, следует поступить примерно так:</p>
<p><code>void *pVectorMemory =// Выделить блок общей памяти,</code></p>
<p><code>mallocShared(sizeof(SharedOoubleVec)); // обьем которой достаточен</code></p>
<p><code>	// для хранения объекта SharedDoubleVec</code></p>
<p><code>SharedDoubleVec *pv =// Использовать "new с явным</code></p>
<p><code>new (pVectorMemory) SharedDoubleVec; // размещением" для создания</code></p>
<p><code>// объекта SharedDoubleVec: </code></p>
<p><code>// см. далее.</code></p>
<p><code>// Использование объекта (через pv)</code></p>
<p><code>pv-&gt;~SharedDoubleVec();// Уничтожить объект в общей памяти</code></p>
<p><code>freeShared(pVectorMemory);// Освободить исходный блок</code></p>
<p><code>// общей памяти</code></p>
<p>Надеюсь, смысл происходящего достаточно ясен из комментариев. В общих чертах происходит следующее: мы выделяем бок общей памяти и конструируем в ней <code>vecto<emphasis>r</emphasis></code><emphasis>, </emphasis>использующий общую память для своих внутренних операций. После завершения работы с вектором мы вызываем его деструктор и освобождаем память, занимаемую вектором. Код не так уж сложен, но все-таки он не сводится к простому объявлению локальной переменной, как прежде. Если у вас нет веских причин для того, чтобы в общей памяти находился сам контейнер (а не его элементы), я рекомендую избегать четырехшагового процесса «выделение/конструирование/уничтожение/освобождение».</p>
<p>Несомненно, вы заметили: в приведенном фрагменте проигнорирована возможность того, что mallocShared может вернуть null. Разумеется, в окончательной версии следовало бы учесть такую возможность. Кроме того, конструирование vector в общей памяти производится конструкцией «new с явным размещением», описанной в любом учебнике по С++.</p>
<p>Рассмотрим другой пример использования распределителей памяти. Предположим, у нас имеются две кучи, представленные классами Heap1 и Неар2. Каждый из этих классов содержит статические функции для выделения и освобождения памяти:</p>
<p><code>class Heap1 {</code></p>
<p><code>public:</code></p>
<p><code>	static void* alloc(size t numBytes, const void* memoryBlockToBeNear);</code></p>
<p><code>	static void dealloc(void *ptr);</code></p>
<p><code>};</code></p>
<p><code>class Heap2 {...}; // Тот же интерфейс alloc/dealloc</code></p>
<p>Далее предположим, что вы хотите разместить содержимое контейнеров STL в заданных кучах. Сначала следует написать распределитель, способный использовать классы Heap1 и <strong>Неар2 </strong>при управлении памятью:</p>
<p><code>template&lt;typename T,typename Heap&gt;</code></p>
<p><code>SpecificHeapAllocator{ </code></p>
<p><code>public:</code></p>
<p><code>...</code></p>
<p><code>pointer allocate(size_type numObjects,const void *localityHint=0) {</code></p>
<p><code>	return static_cast&lt;pointer&gt; (Heap::alloc(numObjects*sizeof(T), localityHint)):</code></p>
<p><code>}</code></p>
<p><code>void deallocate(pointer ptrToMemory,size_type numObjects) {</code></p>
<p><code>	Heap::dealloc(ptrToMemory);</code></p>
<p><code>}</code></p>
<p><code>...</code></p>
<p><code>};</code></p>
<p>Затем <code>SpecialHeapAllocator</code> группирует элементы контейнеров:</p>
<p><code>vector&lt;int.SpecificHeapAllocator&lt;int,Heap1&gt; &gt; v; // Разместить элементы</code></p>
<p><code>set&lt;int,SpecificHeapAllocator&lt;int,Heap1&gt; &gt; s: // v и s в Heapl</code></p>
<empty-line/>
<p><code>list&lt;Widget,</code></p>
<p><code>SpecificHeapAllocator&lt;Widget,Heap2&gt; &gt; L; // Разместить элементы</code></p>
<p><code>map&lt;int.string.less&lt;int&gt;,// L и m в Heap2</code></p>
<p><code>SpecificHeapAllocator&lt;pair&lt;const int,string&gt;. Heap2&gt; &gt; m;</code></p>
<p>В приведенном примере очень важно, чтобы <code>Heap1</code> и <code>Неар2</code> были <emphasis>типами, </emphasis>а не объектами. В STL предусмотрен синтаксис инициализации разных контейнеров STL разными объектами распределителей одного типа, но я не буду его приводить. Дело в том, что если бы <code>Heap1</code> и <code>Неар2</code> были бы <emphasis>объектами </emphasis>вместо типов, это привело бы к нарушению ограничения эквивалентности, подробно описанного в совете 10.</p>
<p>Как показывают приведенные примеры, распределители приносят пользу во многих ситуациях. При соблюдении ограничения об эквивалентности однотипных распределителей у вас не будет проблем с применением нестандартных распределителей для управления памятью, группировки, а также использования общей памяти и других специализированных пулов.</p>
</section>
<section>
<title>
<p>Совет 12. Разумно оценивайте потоковую безопасность контейнеров STL</p>
</title>
<p>Мир стандартного С++ выглядит старомодным и не подверженным веяниям времени. В этом мире все исполняемые файлы компонуются статически, в нем нет ни файлов, отображаемых на память, ни общей памяти. В нем нет графических оконных систем, сетей и баз данных, нет и других процессов. Вероятно, не стоит удивляться тому, что в Стандарте не сказано ни слова о программных потоках. О потоковой безопасности в STL можно уверенно сказать только одно: что она полностью зависит от реализации.</p>
<p>Конечно, многопоточные программы распространены весьма широко, поэтому большинство разработчиков STL стремится к тому, чтобы их реализации хорошо работали в многопоточных условиях. Но даже если они хорошо справятся со своей задачей, основное бремя остается на ваших плечах. Возможности разработчиков STL в этой области ограничены, и вы должны хорошо понимать, где проходят эти границы.</p>
<p>«Золотой стандарт» поддержки многопоточности в контейнерах STL (которым руководствуется большинство разработчиков) был определен компанией SGI и опубликован на ее web-сайте, посвященном STL [21]. Фактически в нем сказано, что в лучшем случае можно надеяться на следующее:</p>
<p>•безопасность параллельного чтения. Несколько потоков могут одновременно читать содержимое контейнера, и это не помешает его правильной работе. Естественно, запись в контейнер при этом не допускается;</p>
<p>•безопасность записи в разные контейнеры. Несколько потоков могут одновременно производить запись в разные контейнеры.</p>
<p>Обращаю ваше внимание: это то, на что вы можете <emphasis><strong>надеяться, </strong></emphasis>но <emphasis><strong>не рассчитывать. </strong></emphasis>Одни реализации предоставляют такие гарантии, другие — нет.</p>
<p>Многопоточное программирование считается сложной задачей, и многие программисты желают, чтобы реализации STL изначально обеспечивали полную потоковую безопасность. Это избавило бы их от необходимости самостоятельно синхронизировать доступ. Конечно, это было бы очень удобно, однако добиться этой цели очень сложно. Рассмотрим несколько способов реализации полной потоковой безопасности контейнеров:</p>
<p>•блокировка контейнера на время вызова любой функции;</p>
<p>•блокировка контейнера в течение жизненного цикла каждого возвращаемого итератора (например посредством вызова begin или end);</p>
<p>•блокировка контейнера на протяжении работы каждого алгоритма, вызванного для этого контейнера. В действительности это бессмысленно, поскольку, как будет показано в совете 32, алгоритм не располагает средствами идентификации контейнера, с которым он работает. Тем не менее, мы изучим этот вариант — будет поучительно увидеть, почему он в принципе неработоспособен.</p>
<p>Рассмотрим следующий фрагмент, который ищет в vector&lt;int&gt; первое вхождение числа 5 и заменяет его нулем:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>vector&lt;int&gt;::iterator first5(find(v.begin(),v.end(),5)); // Строка 1</code></p>
<p><code>if (first5 != v.end()) {// Строка 2</code></p>
<p><code>*first5 = 0;// Строка 3</code></p>
<p><code>}</code></p>
<p>В многопоточной среде существует вероятность того, что другой поток изменит содержимое <code>v</code> сразу же после выполнения строки 1. Если это произойдет, сравнение first5 с <code>v.end</code> в строке 2 становится бессмысленным, поскольку содержимое <code>v</code> будет не тем, каким оно было в конце строки 1. Более того, такая проверка может привести к непредсказуемым результатам, поскольку третий поток может перехватить управление между строками 1 и 2 и сделать first5 недействительным (например, при выполнении вставки вектор может заново выделить память, вследствие чего все итераторы данного вектора станут недействительными. За подробностями о перераспределении памяти обращайтесь к совету 14). Присваивание *first5 в строке 3 тоже небезопасно, поскольку между строками 2 и 3 другой поток может удалить элемент, на который указывает (или, по крайней мере, указывал раньше) итератор first5.</p>
<p>Ни одно из описанных выше решений с блокировкой не решает этих проблем. Вызовы <code>begin</code> и <code>end</code> в строке 1 сразу возвращают управление, сгенерированные ими итераторы остаются действительными только до конца строки, а <code>find</code> тоже возвращает управление в конце строки.</p>
<p>Чтобы этот фрагмент был потоково-безопасным, блокировка <code>v</code> должна сохраняться от строки 1 до строки 3. Трудно представить, каким образом реализация STL могла бы автоматически придти к такому выводу. А если учесть, что использование примитивов синхронизации (семафоров, мьютексов<a l:href="#n1" type="note">[1]</a> и т. д.) обычно сопряжено с относительно высокими затратами, еще труднее представить, каким образом реализация могла бы сделать это без значительного снижения быстродействия по сравнению с программами, которым <emphasis><strong>априорно </strong></emphasis>известно, что в строках 1-3 с <code>v</code> будет работать только один программный поток.</p>
<p>Понятно, почему в решении проблем многопоточности не стоит полагаться на реализацию STL. Вместо этого в подобных случаях следует самостоятельно синхронизировать доступ. В приведенном примере это может выглядеть так:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>getMutexFor(v);</code></p>
<p><code>vector&lt;int&gt;::iterator first5(find(v.begin(),v.end(),5));</code></p>
<p><code>if (first5 != v.end()) {// Теперь эта строка безопасна</code></p>
<p><code>*first5 = 0:// И эта строка тоже</code></p>
<p><code>}</code></p>
<p><code>releaseMutexFor(v);</code></p>
<p>В другом, объектно-ориентированном, решении создается класс <code>Lock</code>, который захватывает мьютекс в конструкторе и освобождает его в деструкторе, что сводит к минимуму вероятность вызова <code>getMutexFor</code> без парного вызова <code>releaseMutexFor</code>. Основа такого класса (точнее, шаблона) выглядит примерно так:</p>
<p><code>template&lt;typename Container&gt; // Базовый шаблон для классов,</code></p>
<p><code>class Lock{// захватывающих и освобождающих мьютексы</code></p>
<p><code>public:// для контейнеров: многие технические</code></p>
<p><code>// детали опущены</code></p>
<p><code>Lock(const Containers container)</code></p>
<p><code>:c(container)</code></p>
<p><code>{</code></p>
<p><code>getMutexFor(с);// Захват мьютекса в конструкторе</code></p>
<p><code>}</code></p>
<p><code>~Lock () {</code></p>
<p><code>releaseMutexFor(c): // Освобождение мьютекса в деструкторе</code></p>
<p><code>}</code></p>
<p><code>private:</code></p>
<p><code>const Container&amp; с;</code></p>
<p>Концепция управления жизненным циклом ресурсов (в данном случае — мьютексов) при помощи специализированных классов вроде <code><strong>Lock</strong></code> рассматривается в любом серьезном учебнике С++. Попробуйте начать с книги Страуструпа (Stroustrup) «The С++ Programming Language» [7], поскольку именно Страуструп популяризировал эту идиому, однако информацию также можно найти в совете 9 «More Effective С++». Каким бы источником вы ни воспользовались, помните, что приведенный выше класс <strong>Lock </strong>урезан до абсолютного минимума. Полноценная версия содержала бы многочисленные дополнения, не имеющие никакого отношения к STL. Впрочем, несмотря на минимализм, приведенная версия <strong>Lock </strong>вполне может использоваться в рассматриваемом примере:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>...</code></p>
<p><code>{// Создание нового блока</code></p>
<p><code>Lock&lt;vector&lt;int&gt; &gt; lock(v); // Получение мьютекса</code></p>
<p><code>vector&lt;int&gt;::iterator first5(find(v.begin().v.end().5));</code></p>
<p><code>if (first5 != v.end()) { </code></p>
<p><code>	*first5 - 0:</code></p>
<p><code>}</code></p>
<p><code>}// Закрытие блока с автоматическим</code></p>
<p><code>// освобождением мьютекса</code></p>
<p>Поскольку мьютекс контейнера освобождается в деструкторе <strong>Lock, </strong>важно обеспечить уничтожение <strong>Lock </strong>сразу же после освобождения мьютекса. Для этого мы создаем новый блок, в котором определяется объект <strong>Lock, </strong>и закрываем его, как только надобность в мьютексе отпадает. На первый взгляд кажется, что вызов <code>releaseMutexFor</code> попросту заменен необходимостью закрыть блок, но это не совсем так. Если мы забудем создать новый блок для <strong>Lock, </strong>мьютекс все равно будет освобожден, но это может произойти позднее положенного момента — при выходе из внешнего блока. Если забыть о вызове <code>releaseMutexFor</code><emphasis><strong>, </strong></emphasis>мьютекс вообще не освобождается.</p>
<p>Более того, решение, основанное на классе <strong>Lock, </strong>лучше защищено от исключений. С++ гарантирует уничтожение локальных объектов при возникновении исключения, поэтому <strong>Lock </strong>освободит мьютекс, даже если исключение произойдет при использовании объекта <strong>Lock. </strong>При использовании парных вызовов <code><strong>getMutexFo</strong><emphasis><strong>r/ r</strong></emphasis><strong>eleaseMutexFor</strong></code> мьютекс не будет освобожден, если исключение происходит после вызова <code>getMutexFor</code><emphasis><strong>, </strong></emphasis>но перед вызовом <code>releaseMutexFor</code><strong>.</strong></p>
<p>Исключения и управление ресурсами важны, но данный совет посвящен другой теме — потоковой безопасности в STL. Как говорилось выше, вы можете <emphasis><strong>надеяться </strong></emphasis>на то, что реализация библиотеки обеспечивает параллельное чтение из одного контейнера и одновременную запись в разные контейнеры. Не надейтесь, что библиотека избавит вас от ручной синхронизации и <emphasis><strong>не рассчитывайте </strong></emphasis>на поддержку многопоточности.</p>
</section>
</section>
<section>
<title>
<p>Контейнеры vector и string</p>
</title>
<section>
<p>Все контейнеры STL по-своему полезны, однако большинство программистов С++ работает с <strong>vector </strong>и <strong>string </strong>чаще, чем с их собратьями, и это вполне понятно. Ведь контейнеры <strong>vector </strong>и <strong>string </strong>разрабатывались как замена массивов, а массивы настолько полезны и удобны, что встречаются во всех коммерческих языках программирования от COBOL до Java.</p>
<p>В этой главе контейнеры <strong>vector </strong>и <strong>string </strong>рассматриваются с нескольких точек зрения. Сначала мы разберемся, чем они превосходят классические массивы STL, затем рассмотрим пути повышения быстродействия <strong>vector </strong>и <strong>string, </strong>познакомимся с различными вариантами реализации <strong>string, </strong>изучим способы передачи <strong>string </strong>и <strong>vector </strong>функциям API, принимающим данные в формате С. Далее будет показано, как избежать лишних операций выделения памяти. Глава завершается анализом поучительной аномалии, <strong>vector&lt;bool&gt;.</strong></p>
</section>
<section>
<title>
<p>Совет 13. Используйте vector и string вместо динамических массивов</p>
</title>
<p>Принимая решение о динамическом выделении памяти оператором <strong>new, </strong>вы берете на себя ряд обязательств.</p>
<p>1.Выделенная память в дальнейшем должна быть освобождена оператором <strong>delete. </strong>Вызов <strong>new </strong>без последующего <strong>delete </strong>приводит к утечке ресурсов.</p>
<p>2.Освобождение должно выполняться соответствующей формой оператора <strong>delete. </strong>Одиночный объект освобождается простым вызовом <strong>delete, </strong>а для массивов требуется форма <strong>delete []</strong>. Ошибка в выборе формы <strong>delete </strong>приводит к непредсказуемым последствиям. На одних платформах программа «зависает» во время выполнения, а на других она продолжает работать с ошибками, приводящими к утечке ресурсов и порче содержимого памяти.</p>
<p>3. Оператор delete для освобождаемого объекта должен вызываться ровно один раз. Повторное освобождение памяти также приводит к непредсказуемым последствиям.</p>
<p>Итак, динамическое выделение памяти сопряжено с немалой ответственностью, и я не понимаю, зачем брать на себя лишние обязательства. При использовании vector и string необходимость в динамическом выделении памяти возникает значительно реже.</p>
<p>Каждый раз, когда вы готовы прибегнуть к динамическому выделению памяти под массив (то есть собираетесь включить в программу строку вида «<code>new T[...]</code>»), подумайте, нельзя ли вместо этого воспользоваться vector или string. Как правило, <code>string</code> используется в том случае, если Т является символьным типом, а <code>vector</code> — во всех остальных случаях. Впрочем, позднее мы рассмотрим ситуацию, когда выбор <code>vector&lt;char&gt;</code> выгладит вполне разумно. Контейнеры vector и string избавляют программиста от хлопот, о которых говорилось выше, поскольку они самостоятельно управляют своей памятью. Занимаемая ими память расширяется по мере добавления новых элементов, а при уничтожении vector или string деструктор автоматически уничтожает элементы контейнера и освобождает память, в которой они находятся.</p>
<p>Кроме того, vector и string входят в семейство последовательных контейнеров STL, поэтому в вашем распоряжении оказывается весь арсенал алгоритмов STL, работающих с этими контейнерами. Впрочем, алгоритмы STL могут использоваться и с массивами, однако у массивов отсутствуют удобные функции begin, end, size и т. п., а также вложенные определения типов (<code>iterator<emphasis><strong>, </strong></emphasis>reverse_iterator<emphasis><strong>, </strong></emphasis>value_type</code> и т. д.), а указатели <code>char*</code> вряд ли могут сравниться со специализированными функциями контейнера string. Чем больше работаешь с STL, тем меньше энтузиазма вызывают встроенные массивы.</p>
<p>Если вас беспокоит судьба унаследованного кода, работающего с массивами, не волнуйтесь и смело используйте <code>vector</code> и <code>string</code>. В совете 16 показано, как легко организовать передачу содержимого <code>vector</code> и <code>string</code> функциям С, работающим с массивами, поэтому интеграция с унаследованным кодом обычно обходится без затруднений.</p>
<p>Честно говоря, мне приходит в голову лишь одна возможная проблема при замене динамических массивов контейнерами <code>vector/string</code>, причем она относится только к string. Многие реализации string основаны на подсчете ссылок (совет 15), что позволяет избавиться от лишних выделений памяти и копирования символов, а также во многих случаях ускоряет работу контейнера. Оптимизация string на основе подсчета ссылок была сочтена настолько важной, что Комитет по стандартизации С++ специально разрешил ее использование.</p>
<p>Впрочем, оптимизация нередко оборачивается «пессимизацией». При использовании string с подсчетом ссылок в многопоточной среде время, сэкономленное на выделении памяти и копировании, может оказаться ничтожно малым по сравнению со временем, затраченным на синхронизацию доступа (за подробностями обращайтесь к статье Саттера «Optimizations That Aren't (In a Multithreaded World)» [20]). Таким образом, при использовании string с подсчетом ссылок в многопоточной среде желательно следить за проблемами быстродействия, обусловленными поддержкой потоковой безопасности.</p>
<p>Чтобы узнать, используется ли подсчет ссылок в вашей реализации string, проще всего обратиться к документации библиотеки. Поскольку подсчет ссылок считается оптимизацией, разработчики обычно отмечают его среди положительных особенностей библиотеки. Также можно обратиться к исходным текстам реализации string. Обычно я не рекомендую искать нужную информацию таким способом, но иногда другого выхода просто не остается. Если вы пойдете по этому пути, не забывайте, что string является определением типа для <code>basic_string&lt;char&gt;</code> (а <code>wstring</code> — для <code>basic_string&lt;wchar_t&gt;</code>), поэтому искать следует в шаблоне <code>basic_string</code>. Вероятно, проще всего обратиться к копирующему конструктору класса. Посмотрите, увеличивает ли он переменную, которая может оказаться счетчиком ссылок. Если такая переменная будет найдена, string использует подсчет ссылок, а если нет — не использует... или вы просто ошиблись при поиске.</p>
<p>Если доступная реализация string построена на подсчете ссылок, а ее использование в многопоточной среде порождает проблемы с быстродействием, возможны по крайней мере три разумных варианта, ни один из которых не связан с отказом от STL. Во-первых, проверьте, не позволяет ли реализация библиотеки отключить подсчет ссылок (обычно это делается изменением значения препроцессорной переменной). Конечно, переносимость при этом теряется, но с учетом минимального объема работы данный вариант все же стоит рассмотреть. Во-вторых, найдите или создайте альтернативную реализацию string (хотя бы частичную), не использующую подсчета ссылок. В-третьих, посмотрите, нельзя ли использовать vector&lt;char&gt; вместо string. Реализации vector не могут использовать подсчет ссылок, поэтому скрытые проблемы многопоточного быстродействия им не присущи. Конечно, при переходе к vector&lt;char&gt; теряются многие удобные функции контейнера string, но большая часть их функциональности доступна через алгоритмы STL, поэтому речь идет не столько о сужении возможностей, сколько о смене синтаксиса.</p>
<p>Из всего сказанного можно сделать простой вывод — массивы с динамическим выделением памяти часто требуют лишней работы. Чтобы упростить себе жизнь, используйте vector и string.</p>
</section>
<section>
<title>
<p>Совет 14. Используйте reserve для предотвращения лишних операций перераспределения памяти</p>
</title>
<p>Одной из самых замечательных особенностей контейнеров STL является автоматическое наращивание памяти в соответствии с объемом внесенных данных (при условии, что при этом не превышается максимальный размер контейнера — его можно узнать при помощи функции max_size). Для контейнеров vector и string дополнительная память выделяется аналогом функции realloc. Процедура состоит из четырех этапов:</p>
<p>1. Выделение нового блока памяти, размер которого кратен текущей емкости контейнера. В большинстве реализаций vector и string используется двукратное увеличение, то есть при каждом выделении дополнительной памяти емкость контейнера увеличивается вдвое.</p>
<p>2.Копирование всех элементов из старой памяти контейнера в новую память.</p>
<p>3.Уничтожение объектов в старой памяти.</p>
<p>4.Освобождение старой памяти.</p>
<p>При таком количестве операций не приходится удивляться тому, что динамическое увеличение контейнера порой обходится довольно дорого. Естественно, эту операцию хотелось бы выполнять как можно реже. А если это еще не кажется естественным, вспомните, что при каждом выполнении перечисленных операций все итераторы, указатели и ссылки на содержимое <code>vector</code> или <code>string</code> становятся недействительными. Таким образом, простая вставка элемента в <code>vector/string</code> может потребовать обновления других структур данных, содержащих итераторы, указатели и ссылки расширяемого контейнера.</p>
<p>Функция reserve позволяет свести к минимуму количество дополнительных перераспределений памяти и избежать затрат на обновление недействительных итераторов/указателей/ссылок. Но прежде чем объяснять, как это происходит, позвольте напомнить о существовании четырех взаимосвязанных функций, которые иногда путают друг с другом. Из всех стандартных контейнеров перечисленные функции поддерживаются только контейнерами vector и string.</p>
<p>•Функция size() возвращает текущее количество элементов в контейнере. Она <emphasis><strong>не сообщает, </strong></emphasis>сколько памяти контейнер выделил для хранящихся в нем элементов.</p>
<p>•Функция capacity() сообщает, сколько элементов поместится в выделенной памяти. Речь идет об <emphasis><strong>общем </strong></emphasis>количестве элементов, а не о том, сколько <emphasis><strong>еще </strong></emphasis>элементов можно разместить без расширения контейнера. Если вас интересует объем свободной памяти vector или string, вычтите size() из capacity(). Если size() и capacity() возвращают одинаковые значения, значит, в контейнере не осталось свободного места, и следующая вставка (insert<emphasis><strong>, </strong></emphasis>push_back и т. д.) вызовет процедуру перераспределения памяти, описанную выше.</p>
<p>•Функция resize(size_t n) изменяет количество элементов, хранящихся в контейнере. После вызова resize функция size вернет значение n. Если n меньше текущего размера, лишние элементы в конце контейнера уничтожаются. Если n больше текущего размера, в конец контейнера добавляются новые элементы, созданные конструктором по умолчанию. Если n больше текущей емкости контейнера, перед созданием новых элементов происходит перераспределение памяти.</p>
<p>•Функция reserve(size_t n) устанавливает минимальную емкость контейнера равной n — при условии, что n не меньше текущего размера. Обычно это приводит к перераспределению памяти вследствие увеличения емкости (если n меньше текущей емкости, vector игнорирует вызов функции и не делает ничего, а string <emphasis><strong>может </strong></emphasis>уменьшить емкость до большей из величин (size(), n)), но размер string при этом заведомо не изменяется. По собственному опыту знаю, что усечение емкости string вызовом reserve обычно менее надежно, чем «фокус с перестановкой», описанный в совете 17.</p>
<p>Из краткого описания функций становится ясно, что перераспределение (выделение и освобождение блоков памяти, копирование и уничтожение объектов, обновление недействительных итераторов, указателей и ссылок) происходит каждый раз, когда при вставке нового элемента текущая емкость контейнера оказывается недостаточной. Таким образом, для предотвращения лишних затрат следует установить достаточно большую емкость контейнера функцией reserve, причем сделать это нужно как можно раньше — желательно сразу же после конструирования контейнера.</p>
<p>Предположим, вы хотите создать vector&lt;int&gt; с числами из интервала 1-1000. Без использования reserve это делалось бы примерно так:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>for (int i=l; i&lt;=1000: ++i) v.push_back(i):</code></p>
<p>В большинстве реализаций STL при выполнении этого фрагмента произойдет от 2 до 10 расширений контейнера. Кстати, число 10 объясняется очень просто. Вспомните, что при каждом перераспределении емкость vector обычно увеличивается вдвое, а 1000 примерно равно 2<sup>10</sup>.</p>
<p>vector&lt;int&gt; v;</p>
<p>reserve(1000);</p>
<p>for (int i=1;i&lt;=1000: ++i)  v.push_back(i);</p>
<p>В этом случае количество расширений будет равно нулю.</p>
<p>Взаимосвязь между size и capacity позволяет узнать, когда вставка в vector или string приведет к расширению контейнера. В свою очередь, это позволяет предсказать, когда вставка приведет к недействительности итераторов, указателей и ссылок в контейнере. Пример:</p>
<p><code>string s;</code></p>
<p><code>if (s.size() &lt; s.capacity()) {</code></p>
<p><code>	s.push_back('x');</code></p>
<p><code>}</code></p>
<p>В этом фрагменте вызов push_back не может привести к появлению недействительных итераторов, указателей и ссылок, поскольку емкость string заведомо больше текущего размера. Если бы вместо push_back выполнялась вставка в произвольной позиции строки функцией insert, это также гарантировало бы отсутствие перераспределений памяти, но в соответствии с обычными правилами действительности итераторов для вставки в string все итераторы/указатели/ссылки от точки вставки до конца строки стали бы недействительными.</p>
<p>Вернемся к основной теме настоящего совета. Существуют два основных способа применения функции reserve для предотвращения нежелательного перераспределения памяти. Первый способ используется в ситуации, когда известно точное или приблизительное количество элементов в контейнере. В этом случае, как в приведенном выше примере с vector, нужный объем памяти просто резервируется заранее. Во втором варианте функция reserve резервирует максимальный объем памяти, который может понадобиться, а затем после включения данных в контейнер вся свободная память освобождается. В усечении свободной памяти нет ничего сложного, однако я не буду описывать эту операцию здесь, потому что в ней используется особый прием, рассмотренный в совете 17.</p>
</section>
<section>
<title>
<p>Совет 15. Помните о различиях в реализации string</p>
</title>
<p>Бьерн Страуструп однажды написал статью с интригующим названием «Sixteen Ways to Stack a Cat» [27], в которой были представлены разные варианты реализации стеков. Оказывается, по количеству возможных реализаций контейнеры string не уступают стекам. Конечно, нам, опытным и квалифицированным программистам, положено презирать «подробности реализации», но если Эйнштейн был прав, и Бог действительно проявляется в мелочах... Даже если подробности действительно несущественны, в них все же желательно разбираться. Только тогда можно быть полностью уверенным в том, что они <emphasis><strong>действительно </strong></emphasis>несущественны.</p>
<p>Например, сколько памяти занимает объект string? Иначе говоря, чему равен результат sizeof(string)? Ответ на этот вопрос может быть весьма важным, особенно если вы внимательно следите за расходами памяти и думаете о замене низкоуровневого указателя cha<emphasis><strong>r* </strong></emphasis>объектом string.</p>
<p>Оказывается, результат sizeof (string) неоднозначен — и если вы действительно следите за расходами памяти, вряд ли этот ответ вас устроит. Хотя у некоторых реализаций контейнер string по размеру совпадает с char*, так же часто встречаются реализации, у которой string занимает в семь раз больше памяти. Чем объясняются подобные различия? Чтобы понять это, необходимо знать, какие данные и каким образом будут храниться в объекте string.</p>
<p>Практически каждая реализация string хранит следующую информацию:</p>
<p>•размер строки, то есть количество символов;</p>
<p>•емкость блока памяти, содержащего символы строки (различия между размером и емкостью описаны в совете 14);</p>
<p>•содержимое строки, то есть символы, непосредственно входящие в строку. Кроме того, в контейнере string может храниться:</p>
<p>•копия распределителя памяти. В совете 10 рассказано, почему это поле не является обязательным. Там же описаны странные правила, по которым работают распределители памяти.</p>
<p>Реализации string, основанные на подсчете ссылок, также содержат:</p>
<p>•счетчик ссылок для текущего содержимого.</p>
<p>В разных реализациях string эти данные хранятся по-разному. Для наглядности мы рассмотрим структуры данных, используемые в четырех вариантах реализации string. В выборе нет ничего особенного, все варианты позаимствованы из широко распространенных реализаций STL. Просто они оказались первыми, попавшимися мне на глаза.</p>
<p>В реализации А каждый объект string содержит копию своего распределителя памяти, размер строки, ее емкость и указатель на динамически выделенный буфер со счетчиком ссылок (<code>RefCnt</code>) и содержимым строки. В этом варианте объект string, использующий стандартный распределитель памяти, занимает в четыре раза больше памяти по сравнению с указателем. При использовании нестандартного указателя объект string увеличится на размер объекта распределителя.</p>
<image l:href="#img_2.png"/>
<empty-line/>
<p>В реализации В объекты string по размерам не отличаются от указателей, поскольку они содержат указатель на структуру. При этом также предполагается использование стандартного распределителя памяти. Как и в реализации А, при использовании нестандартного распределителя размер объекта string увеличивается на размер объекта распределителя. Благодаря оптимизации, присутствующей в этом варианте, но не предусмотренной в варианте А, использование стандартного распределителя обходится без затрат памяти.</p>
<p>В объекте, на который ссылается указатель, хранится размер строки, емкость и счетчик ссылок, а также указатель на динамически выделенный буфер с текущим содержимым строки. Здесь же хранятся дополнительные данные, относящиеся к синхронизации доступа в многопоточных системах. К нашей теме они не относятся, поэтому на рисунке соответствующая часть структуры данных обозначена «Прочее».</p>
<image l:href="#img_3.png"/>
<empty-line/>
<p>Блок «Прочее» оказался больше остальных блоков, поскольку я постарался выдержать масштаб изображения. Если один блок вдвое больше другого, значит, он занимает вдвое больше памяти. В реализации В размер данных синхронизации примерно в шесть раз превышает размер указателя.</p>
<p>В реализации С размер объекта string всегда равен размеру указателя, но этот указатель всегда ссылается на динамически выделенный буфер, содержащий все данные строки: размер, емкость, счетчик ссылок и текущее содержимое. Распределители уровня объекта не поддерживаются. В буфере также хранятся данные, описывающие возможности совместного доступа к содержимому; эта тема здесь не рассматривается, поэтому соответствующий блок на рисунке помечен буквой «X» (если вас интересует, зачем может потребоваться ограничение доступа к данным с подсчетом ссылок, обратитесь к совету 29 «More Effective С++»).</p>
<image l:href="#img_4.png"/>
<empty-line/>
<p>В реализации D объекты string занимают в семь раз больше памяти, чем указатель (при использовании стандартного распределителя памяти). В этой реализации подсчет ссылок не используется, но каждый объект string содержит внутренний буфер, в котором могут храниться до 15 символов. Таким образом, небольшие строки хранятся непосредственно в объекте string — данная возможность иногда называется «оптимизацией малых строк». Если емкость строки превышает 15 символов, в начале буфера хранится указатель на динамически выделенный блок памяти, в котором содержатся символы строки.</p>
<image l:href="#img_5.png"/>
<empty-line/>
<p>Я поместил здесь эти диаграммы совсем не для того, чтобы убедить читателя в своем умении читать исходные тексты и рисовать красивые картинки. По ним также можно сделать вывод, что создание объекта string командами вида</p>
<p><code>string s("Perse"); // Имя нашей собаки - Персефона, но мы</code></p>
<p><code>// обычно зовем ее просто "Перси"</code></p>
<p>в реализации D обходится без динамического выделения памяти, обходится одним выделением в реализациях А и С и двумя — в реализации В (для объекта, на который ссылается указатель string, и для символьного буфера, на который ссылается указатель в этом объекте). Если для вас существенно количество операций выделения/освобождения или затраты памяти, часто связанные с этими операциями, от реализации В лучше держаться подальше. С другой стороны, наличие специальной поддержки синхронизации доступа в реализации В может привести к тому, что эта реализация подойдет для ваших целей лучше, чем реализации А и С, а количество динамических выделений памяти уйдет на второй план. Реализация D не требует специальной поддержки многопоточности, поскольку в ней не используется подсчет ссылок. За дополнительной информацией о связи между многопоточностью и строками с подсчетом ссылок обращайтесь к совету 13. Типичная поддержка многопоточности в контейнерах STL описана в совете 12.</p>
<p>В архитектуре, основанной на подсчете ссылок, все данные, находящиеся за пределами объекта string, могут совместно использоваться разными объектами string (имеющими одинаковое содержимое), поэтому из приведенных диаграмм также можно сделать вывод, что реализация А обладает меньшими возможностями для совместного использования данных. В частности, реализации В и С допускают совместное использование данных размера и емкости объекта, что приводит к потенциальному уменьшению затрат на хранение этих данных на уровне объекта. Интересно и другое: отсутствие поддержки распределителей уровня объекта в реализации С означает, что это единственная реализация с возможностью использования общих распределителей: все объекты string должны работать с одним распределителем! (За информацией о принципах работы распределителей обращайтесь к совету 10.) Реализация D не позволяет совместно использовать данные в объектах string.</p>
<p>Один из интересных аспектов поведения string, не следующий непосредственно из этих диаграмм, относится к стратегии выделения памяти для малых строк. В некоторых реализациях устанавливается минимальный размер выделяемого блока памяти; к их числу принадлежат реализации А, С и D. Вернемся к команде</p>
<p><code>string s ("Perse"); // Строка s состоит из 5 символов</code></p>
<p>В реализации А минимальный размер выделяемого буфера равен 32 символам. Таким образом, хотя размер s во всех реализациях равен 5 символам, емкость этого контейнера в реализации А равна 31 (видимо, 32-й символ зарезервирован для завершающего нуль-символа, упрощающего реализацию функции c_str). В реализации С также установлен минимальный размер буфера, равный 16, при этом место для завершающего нуль-символа не резервируется, поэтому в реализации С емкость s равна 16. Минимальный размер буфера в реализации D также равен 16, но с резервированием места для завершающего нуль-символа. Принципиальное отличие реализации D заключается в том, что содержимое строк емкостью менее 16 символов хранится в самом объекте string. Реализация В не имеет ограничений на минимальный размер выделяемого блока, и в ней емкость s равна 7. (Почему не 6 или 5? Не знаю. Простите, я не настолько внимательно анализировал исходные тексты.)</p>
<p>Из сказанного очевидно следует, что стратегия выделения памяти для малых строк может сыграть важную роль, если вы собираетесь работать с большим количеством коротких строк и (1) в вашей рабочей среде не хватает памяти или (2) вы стремитесь по возможности локализовать ссылки и пытаетесь сгруппировать строки в минимальном количестве страниц памяти.</p>
<p>Конечно, в выборе реализации string разработчик обладает большей степенью свободы, чем кажется на первый взгляд, причем эта свобода используется разными способами. Ниже перечислены ишь некоторые переменные факторы.</p>
<p>• По отношению к содержимому string может использоваться (или не использоваться) подсчет ссылок. По умолчанию во многих реализациях подсчет ссылок включен, но обычно предоставляется возможность его отключения (как правило, при помощи препроцессорного макроса). В совете 13 приведен пример специфической ситуации, когда может потребоваться отключение подсчета ссылок, но такая необходимость может возникнуть и по другим причинам. Например, подсчет ссылок экономит время лишь при частом копировании строк. Если в приложении строки копируются редко, затраты на подсчет ссылок не оправдываются.</p>
<p>•Объекты string занимают в 1-7 (по меньшей мере) раз больше памяти, чем указатели char*.</p>
<p>•Создание нового объекта string может потребовать нуля, одной или двух операций динамического выделения памяти.</p>
<p>•Объекты string могут совместно использовать данные о размере и емкости строки.</p>
<p>•Объекты string могут поддерживать (или не поддерживать) распределители памяти уровня объекта.</p>
<p>•В разных реализациях могут использоваться разные стратегии ограничения размеров выделяемого блока.</p>
<p>Только не поймите меня превратно. Я считаю, что контейнер string является одним из важнейших компонентов стандартной библиотеки и рекомендую использовать его как можно чаще. Например, совет 13 посвящен возможности использования string вместо динамических символьных массивов. Но для эффективного использования STL необходимо разбираться во всем разнообразии реализаций string, особенно если ваша программа должна работать на разных платформах STL при жестких требованиях к быстродействию.</p>
<p>Кроме того, на концептуальном уровне контейнер string выглядел предельно просто. Кто бы мог подумать, что его реализация таит столько неожиданностей?</p>
</section>
<section>
<title>
<p>Совет 16. Научитесь передавать данные vector и string функциям унаследованного интерфейса</p>
</title>
<p>С момента стандартизации С++ в 1998 году элита С++ настойчиво подталкивает программистов к переходу с массивов на vector. Столь же открыто пропагандируется переход от указателей char* к объектам string. В пользу перехода имеются достаточно веские аргументы, в том числе ликвидация распространенных ошибок программирования (совет 13) и возможность полноценного использования всей мощи алгоритмов STL (совет 31).</p>
<p>Но на этом пути остаются некоторые препятствия, из которых едва ли не самым распространенным являются унаследованные интерфейсы языка С, работающие с массивами и указателями char* вместо объектов vector и string. Они существуют с давних времен, и если мы хотим эффективно использовать STL, придется как-то уживаться с этими «пережитками прошлого».</p>
<p>К счастью, задача решается просто. Если у вас имеется vector v и вы хотите получить указатель на данные v, которые интерпретировались бы как массив, воспользуйтесь записью &amp;v[0]. Для string s аналогичная запись имеет вид s.c_str(). Впрочем, это не все — существуют некоторые ограничения (то, о чем в рекламе обычно пишется самым мелким шрифтом).</p>
<p>Рассмотрим следующее объявление:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p>Выражение v[0] дает ссылку на первый элемент вектора, соответственно &amp;v[0] — указатель на первый элемент. В соответствии со Стандартом С++ элементы vector должны храниться в памяти непрерывно, по аналогии с массивом. Допустим, у нас имеется функция С, объявленная следующим образом:</p>
<p><code>void doSomething(const int* pInts, size_t numlnts):</code></p>
<p>Передача данных должна происходить так:</p>
<p><code>doSomething(&amp;v[0],v.size());</code></p>
<p>Во всяком случае, так <emphasis><strong>должно </strong></emphasis>быть. Остается лишь понять, что произойдет, если вектор v пуст. В этом случае функция v.size() вернет 0, а &amp;v[0] пытается получить указатель на несуществующий блок памяти с непредсказуемыми последствиями. Нехорошо. Более надежный вариант вызова выглядит так:</p>
<p><code>if (!v.empty()) {</code></p>
<p><code>	doSomething(&amp;v[0],v.size());</code></p>
<p><code>}</code></p>
<p>Отдельные подозрительные личности утверждают, что &amp;v[0] можно заменить на <code>v.begin()</code>, поскольку begin возвращает итератор, а для vector итератор в действительности представляет собой указатель. Во многих случаях это действительно так, но, как будет показано в совете 50, это правило соблюдается не всегда, и полагаться на него не стоит. Функция begin возвращает итератор, а не указатель, поэтому она никогда не должна использоваться для получения указателя на данные vector. А если уж вам очень приглянулась запись v. begin(), используйте конструкцию &amp;*v.begin() — она вернет тот же указатель, что и &amp;v[0], хотя это увеличивает количество вводимых символов и затрудняет работу людей, пытающихся разобраться в вашей программе. Если знакомые вам советуют использовать <code>v.begin()</code> вместо &amp;v[0] — лучше смените круг общения.</p>
<p>Способ получения указателя на данные контейнера, хорошо работающий для vector, недостаточно надежен для string. Во-первых, контейнер string не гарантирует хранения данных в непрерывном блоке памяти; во-вторых, внутреннее представление строки не обязательно завершается нуль-символом. По этим причинам в контейнере string предусмотрена функция c_str, которая возвращает указатель на содержимое строки в формате С. Таким образом, передача строки s функции</p>
<p><code>void doSomething(const char *pString);</code></p>
<p>происходит так:</p>
<p><code>doSomething(s.c_str());</code></p>
<p>Данное решение подходит и для строк нулевой длины. В этом случае c_str возвращает указатель на нуль-символ. Кроме того, оно годится и для строк с внутренними нуль-символами, хотя в этом случае <code>doSomething</code> с большой вероятностью интерпретирует первый внутренний нуль-символ как признак конца строки. Присутствие внутренних нуль-символов несущественно для объектов string, но не для функций С, использующих char*</p>
<p>Вернемся к объявлениям doSomething:</p>
<p><code>void doSomething(const int* pints, size_t numInts);</code></p>
<p><code>void doSomething(const char *pString);</code></p>
<p>В обоих случаях передаются указатели на const Функция С, получающая данные vector или string, <emphasis><strong>читает </strong></emphasis>их, не пытаясь модифицировать. Такой вариант наиболее безопасен. Для string он неизбежен, поскольку не существует гарантии, что c_str вернет указатель на внутреннее представление строковых данных; функция может вернуть указатель на неизменяемую <emphasis><strong>копию </strong></emphasis>данных в формате С (если вас встревожила эффективность этих операций, не волнуйтесь — мне не известна ни одна современная реализация библиотеки, в которой бы использовалась данная возможность).</p>
<p>Vector предоставляет программисту чуть большую свободу действий. Передача v функции С, модифицирующей элементы v, обычно обходится без проблем, но вызванная функция не должна изменять количество элементов в векторе. Например, она не может «создавать» новые элементы в неиспользуемой памяти vector. Такие попытки приведут к нарушению логической целостности контейнера v, поскольку объект не будет знать свой правильный размер, и вызов функции v.size()возвратит неправильные результаты. А если вызванная функция попытается добавить новые данные в вектор, у которого текущий размер совпадает с емкостью (совет 14), произойдет сущий кошмар. Я даже не пытаюсь предугадать последствия, настолько они ужасны.</p>
<p>Вы обратили внимание на формулировку «обычно обходится без проблем» в предыдущем абзаце? Конечно, обратили. Некоторые векторы устанавливают для своих данных дополнительные ограничения, и при передаче вектора функции API, изменяющей его содержимое, вы должны проследить за тем, чтобы эти ограничения не были нарушены. Например, как объясняется в совете 23, сортируемые векторы часто могут рассматриваться в качестве разумной альтернативы для ассоциативных контейнеров, но при этом содержимое таких векторов должно оставаться правильно отсортированным. При передаче сортируемого вектора функции, способной изменить его содержимое, вам придется учитывать, что при возвращении из функции сортировка элементов может быть нарушена.</p>
<p>Если у вас имеется vector, который должен инициализироваться внутри функции С, можно воспользоваться структурной совместимостью vector с массивами и передать функции указатель на блок элементов вектора:</p>
<p><code>// Функция fillArray получает указатель на массив.</code></p>
<p><code>// содержащий не более arraySize чисел типа double.</code></p>
<p><code>// и записывает в него данные. Возвращаемое количество записанных</code></p>
<p><code>// чисел заведомо не превышает maxNumDoubles.</code></p>
<p><code>size_t fillArray(double *pArray, size_t arraySize);</code></p>
<p><code>vector&lt;double&gt; vd(maxNumDoubles); // Создать vector, емкость которого</code></p>
<p><code>// равна maxNumDoubles</code></p>
<p><code>vd.resize(fillArray(&amp;vd[0], vd.size())); // Заполнить vd вызовом</code></p>
<p><code>// функции fillArray. после чего // изменить размер по количеству // записанных элементов</code></p>
<p>Данный способ подходит только для vector, поскольку только этот контейнер заведомо совместим с массивами по структуре памяти. Впрочем, задача инициализации string функцией С тоже решается достаточно просто. Данные, возвращаемые функцией, заносятся в vector&lt;char&gt; и затем копируются из вектора в string:</p>
<p><code>// Функция получает указатель на массив, содержащий не более </code></p>
<p><code>// arraySize символов, и записывает в него данные. </code></p>
<p><code>// Возвращаемое количество записанных чисел заведомо не превышает </code></p>
<p><code>// maxNumChars</code></p>
<p><code>size_t fillString(char *pArray. sizet arraySize);</code></p>
<p><code>vector&lt;char&gt; vc(maxNumChars); // Создать vector, емкость которого</code></p>
<p><code>// равна maxNumChars </code></p>
<p><code>size_t charsWritten = fillString(&amp;vc[0],vc.size());</code></p>
<p><code>// Заполнить vc</code></p>
<p><code>// вызовом fillString string s(vc.begin().vc.begin()+charsWritten); </code></p>
<p><code>// Скопировать данные</code></p>
<p><code>// из vc в s интервальным </code></p>
<p><code>// конструктором (совет 5)</code></p>
<p>Собственно, сам принцип сохранения данных функцией API в vector и их последующего копирования в нужный контейнер STL работает всегда:</p>
<p><code>size_t fillArray(double *pArray. size_t arraySize); // См. ранее</code></p>
<p><code>vector&lt;double&gt; vd(maxNumDoubles);// Также см. ранее</code></p>
<p><code>vd.resize(fillArray(&amp;vd[0],vd.size());</code></p>
<p><code>deque&lt;double&gt; d(vd.begin().vd.end());// Копирование в deque</code></p>
<p><code>list&lt;double&gt; l(vd.begin().vd.end());// Копирование в list</code></p>
<p><code>set&lt;double&gt; s(vd.begin(),vd.end()):// Копирование в set</code></p>
<p>Более того, этот фрагмент подсказывает, как организовать передачу данных из других контейнеров STL, кроме vector и string, функциям С. Для этого достаточно скопировать данные контейнера в vector и передать его при вызове:</p>
<p><code>void doSomething(const int* pints. size_t numlnts); // Функция С (см. ранее)</code></p>
<p><code>set&lt;int&gt; intSet:</code></p>
<p><code>// Множество, в котором</code></p>
<p><code>// хранятся передаваемые</code></p>
<p><code>// данные</code></p>
<p><code>vector&lt;int&gt; v(intSet.begin(),intSet.end());// Скопировать данные</code></p>
<p><code>// из set в vector</code></p>
<p><code>if (!v.empty()) doSomething(&amp;v[0],v.size());// Передать данные</code></p>
<p><code>// функции С</code></p>
<p>Вообще говоря, данные также можно скопировать в массив и передать их функции С, но зачем это нужно? Если размер контейнера не известен на стадии компиляции, память придется выделять динамически, а в совете 13 объясняется, почему вместо динамических массивов следует использовать vector.</p>
</section>
<section>
<title>
<p>Совет 17. Используйте «фокус с перестановкой» для уменьшения емкости</p>
</title>
<p>Предположим, вы пишете программу для нового телешоу «Бешеные деньги». Информация о потенциальных участниках хранится в векторе:</p>
<p><code>class Contestant {...};</code></p>
<p><code>vector&lt;Contestant&gt; contestants;</code></p>
<p>При объявлении набора участников заявки сыплются градом, и вектор быстро заполняется элементами. Но по мере отбора перспективных кандидатов относительно небольшое количество элементов перемещается в начало вектора (вероятно, вызовом partial_sort или partition — см. совет 31), а неудачники удаляются из вектора (как правило, при помощи интервальной формы erase — см. совет 5). В результате удаления длина вектора уменьшается, но емкость остается прежней. Если в какой-то момент времени вектор содержал данные о 100 000 кандидатов, то его емкость останется равной 100 000, даже если позднее количество элементов уменьшится до 10.</p>
<p>Чтобы вектор не удерживал ненужную память, необходимы средства, которые бы позволяли сократить емкость от максимальной до используемой в настоящий момент. Подобное сокращение емкости обычно называется «сжатием по размеру». Сжатие по размеру легко программируется, однако код — как бы выразиться поделикатнее? — выглядит недостаточно интуитивно. Давайте разберемся, как он работает.</p>
<p>Усечение лишней емкости в векторе contestants производится следующим образом:</p>
<p><code>vector&lt;Contestant&gt;(contestants).swap(contestants);</code></p>
<p>Выражение vector&lt;Contestant&gt;(contestants) создает временный вектор, содержащий копию contestants; основная работа выполняется копирующим конструктором vector. Тем не менее, копирующий конструктор vector выделяет <emphasis><strong>ровно столько памяти, сколько необходимо для хранения копируемых элементов, </strong></emphasis>поэтому временный вектор не содержит лишней емкости. Затем содержимое вектора contestants меняется местами с временным вектором функцией swap. После завершения этой операции в contestants оказывается содержимое временного вектора с усеченной емкостью, а временный вектор содержит «раздутые» данные, ранее находившиеся в contestants. В этот момент (то есть в конце команды) временный вектор уничтожается, освобождая память, ранее занимаемую вектором contestants.</p>
<p>Аналогичный прием применяется и по отношению к строкам:</p>
<p><code>string s;</code></p>
<p><code>// Создать большую строку и удалить из нее // большую часть символов</code></p>
<p><code>string(s).swap(s);// Выполнить "сжатие по размеру" с объектом s</code></p>
<p>Я не могу предоставить стопроцентной гарантии того, что этом прием действительно удалит из контейнера лишнюю емкость. Авторы реализаций при желании могут намеренно выделить в контейнерах vector и string лишнюю память, и иногда они так и поступают. Например, контейнер может обладать минимальной емкостью или же значения емкости vector/string могут ограничиваться степенями 2 (по собственному опыту могу сказать, что подобные аномалии чаще встречаются в реализациях string, нежели в реализациях vector. За примерами обращайтесь к совету 15). Таким образом, «сжатие по размеру» следует понимать не как «приведение к минимальной емкости», а как «приведение к минимальной емкости, допускаемой реализацией для текущего размера контейнера». Впрочем, это лучшее, что вы можете сделать (не считая перехода на другую реализацию STL), поэтому «сжатие по размеру» для контейнеров vector и string фактически эквивалентно «фокусу с перестановкой».</p>
<p>Кстати говоря, одна из разновидностей «фокуса с перестановкой» может использоваться для очистки контейнера с одновременным сокращением емкости до минимальной величины, поддерживаемой вашей реализацией. Для этого в перестановке используется временный объект vector или string, содержимое которого создается конструктором по умолчанию:</p>
<p><code>vector&lt;Contestant&gt; v;</code></p>
<p><code>string s;</code></p>
<p><code>// Использовать v и s</code></p>
<p><code>vector &lt;Contestant&gt;().swap(v); // Очистить v с уменьшением емкости</code></p>
<p><code>string().swap(s);// Очистить s с уменьшением емкости</code></p>
<p>Остается сделать последнее замечание, относящееся к функции <code>swap</code> в целом. Перестановка содержимого двух контейнеров также приводит к перестановке их итераторов, указателей и ссылок. Итераторы, указатели и ссылки, относившиеся к элементам одного контейнера, после вызова <code>swap</code> остаются действительными и указывают на те же элементы — <emphasis><strong>но в другом контейнере.</strong></emphasis></p>
</section>
<section>
<title>
<p>Совет 18. Избегайте vector&lt;bool&gt;</p>
</title>
<p>Vector&lt;bool&gt; как контейнер STL обладает лишь двумя недостатками. Во-первых, это вообще не контейнер STL. Во-вторых, он не содержит bool.</p>
<p>Объект не становится контейнером STL только потому, что кто-то назвал его таковым — он становится контейнером STL лишь при соблюдении всех требований, изложенных в разделе 23.1 Стандарта С++. В частности, в этих требованиях говорится, что если с — контейнер объектов типа Т, поддерживающий оператор <strong>[]</strong>, то следующая конструкция должна нормально компилироваться:</p>
<p><code>Т *р = &amp;с[0];// Инициализировать Т* адресом данных,</code></p>
<p><code>// возвращаемых оператором []</code></p>
<p>Иначе говоря, если оператор [ ] используется для получения одного из объектов Т в Container&lt;T&gt;, то указатель на этот объект можно получить простым взятием его адреса (предполагается, что оператор &amp; типа Т не был перегружен извращенным способом). Следовательно, чтобы vector&lt;bool&gt; был контейнером, следующий фрагмент должен компилироваться:</p>
<p><code>vector&lt;bool&gt; v;</code></p>
<p><code>bool *pb = &amp;v[0]: // Инициализировать bool* адресом результата.</code></p>
<p><code>// возвращаемого оператором vector&lt;bool&gt;::operator[]</code></p>
<p>Однако приведенный фрагмент компилироваться не будет. Дело в том, что <code>vector&lt;bool&gt;</code> — псевдоконтейнер, содержащий не настоящие логические величины bool, а их представления, упакованные для экономии места. В типичной реализации каждая псевдовеличина «bool», хранящаяся в псевдовекторе, занимает один бит, а восьмибитовый байт представляет восемь «bool». Во внутреннем представлении <code>vector&lt;bool&gt;</code> булевы значения, которые должны храниться в контейнере, представляются аналогами битовых полей.</p>
<p>Битовые поля, как и bool, принимают только два возможных значения, но между «настоящими» логическими величинами и маскирующимися под них битовыми полями существует принципиальное различие. Создать указатель на реальное число bool можно, но указатели на отдельные биты запрещены.</p>
<p>Ссылки на отдельные биты тоже запрещены, что представляет определенную проблему для дизайна интерфейса vector&lt;bool&gt;, поскольку функция <code>vector&lt;T&gt;::operator[]</code> должна возвращать значение типа Т&amp;. Если бы контейнер vector&lt;bool&gt; действительно содержал bool, этой проблемы не существовало бы, но вследствие особенностей внутреннего представления функция <code>vector&lt;T&gt;::operator[]</code> должна вернуть несуществующую ссылку на отдельный бит.</p>
<p>Чтобы справиться с этим затруднением, функция <code>vector&lt;T&gt;::operator[]</code> возвращает объект, который <emphasis><strong>имитирует </strong></emphasis>ссылку на отдельный бит — так называемый <emphasis><strong>промежуточный объект. </strong></emphasis>Для использования STL не обязательно понимать, как работают промежуточные объекты, но вообще это весьма полезная идиома С++. Дополнительная информация о промежуточных объектах приведена в совете 30 «More Effective С++», а также в разделе «Паттерн Ргоху» книги «<emphasis>Приемы объектно-ориентированного проектирования</emphasis>» [6]. На простейшем уровне vector&lt;bool&gt; выглядит примерно так:</p>
<p><code>template &lt;typename Allocator&gt;</code></p>
<p><code>vector&lt;bool.Allocator&gt; {</code></p>
<p><code>public:</code></p>
<p><code>class reference {…};// Класс, генерирующий промежуточные</code></p>
<p><code>	// объекты для ссылок на отдельные биты</code></p>
<p><code>reference operator[](size_type n); // operator[] возвращает</code></p>
<p><code>…	// промежуточный объект</code></p>
<p><code>};</code></p>
<p>Теперь понятно, почему следующий фрагмент не компилируется: <code>vector&lt;bool&gt; v;</code></p>
<p><code>bool *pb=&amp;v[0]; // Ошибка! Выражение в правой части относится к типу</code></p>
<p><code>// vector&lt;bool&gt;::reference*, а не bool*</code></p>
<p>А раз фрагмент не компилируется, <code>vector&lt;bool&gt;</code> не удовлетворяет требованиям к контейнерам STL. Да, специфика <code>vector&lt;bool&gt;</code> особо оговорена в Стандарте; да, этот контейнер <emphasis><strong>почти </strong></emphasis>удовлетворяет требованиям к контейнерам STL, но «почти» не считается. Чем больше вы напишете шаблонов, предназначенных для работы с STL, тем глубже вы осознаете эту истину. Уверяю вас, наступит день, когда написанный вами шаблон будет работать лишь в том случае, если получение адреса элемента контейнера дает указатель на тип элемента; и когда этот день придет, вы наглядно ощутите разницу между контейнером и <emphasis><strong>почти </strong></emphasis>контейнером.</p>
<p>Спрашивается, почему же <code>vector&lt;bool&gt;</code> присутствует в Стандарте, если это не контейнер? Отчасти это связано с одним благородным, но неудачным экспериментом, но позвольте мне ненадолго отложить эту тему и заняться более насущным вопросом. Итак, от <code>vector&lt;bool&gt;</code> следует держаться подальше, потому что это не контейнер — но что же делать, когда вам действительно <emphasis><strong>понадобится </strong></emphasis>вектор логических величин?</p>
<p>В стандартную библиотеку входят два альтернативных решения, которые подходят практически для любых ситуаций. Первое решение — <code>deque&lt;bool&gt;</code>. Контейнер deque обладает практически всеми возможностями vector (за исключением разве что reserve и capacity), но при этом deque&lt;bool&gt; является полноценным контейнером STL, содержащим настоящие значения bool. Конечно, внутренняя память deque не образует непрерывный блок, поэтому данные deque&lt;bool&gt; не удастся передать функции С, получающей массив bool (см. совет 16), но это не удалось бы сделать и с vector&lt;bool&gt; из-за отсутствия переносимого способа получения данных vector&lt;bool&gt;. (Прием, продемонстрированный для vector в совете 16, не компилируется для vector&lt;bool&gt;, поскольку он зависит от возможности получения на тип элемента, хранящегося в векторе, — как упоминалось выше, vector&lt;bool&gt; не содержит bool.)</p>
<p>Второй альтернативой для vector&lt;bool &gt; является bitset. Вообще говоря, bitset не является стандартным контейнером STL, но входит в стандартную библиотеку С++. В отличие от контейнеров STL, размер bitset (количество элементов) фиксируется на стадии компиляции, поэтому операции вставки-удаления элементов не поддерживаются. Более того, поскольку bitset не является контейнером STL, в нем отсутствует поддержка итераторов. Тем не менее bitset, как и vector&lt;bool&gt;, использует компактное представление каждого элемента одним битом, поддерживает функцию flip контейнера vector&lt;bool&gt; и ряд других специальных функций, имеющих смысл в контексте битовых множеств. Если вы переживете без итераторов и динамического изменения размеров, вероятно, bitset хорошо подойдет для ваших целей.</p>
<p>А теперь вернемся к благородному, но неудачному эксперименту, из-за которого появился «псевдоконтейнер» vector&lt;bool&gt;. Я уже упоминал о том, что промежуточные объекты часто используются при программировании на С++. Члены Комитета по стандартизации С++ знали об этом, поэтому они решили создать vector&lt;bool&gt; как наглядный пример контейнера, доступ к элементам которого производится через промежуточные объекты. Предполагалось, что при наличии такого примера в Стандарте у программистов появится готовый образец для построения собственных аналогов.</p>
<p>В итоге выяснилось, что создать контейнер с промежуточными объектами, удовлетворяющий всем требованиям к контейнеру STL, <emphasis><strong>невозможно. </strong></emphasis>Так или иначе, следы этой неудачной попытки сохранились в Стандарте. Можно долго гадать, почему vector&lt;bool &gt; был сохранен, но с практической точки зрения это несущественно. Главное — помните, что vector&lt;bool&gt; не удовлетворяет требованиям к контейнерам STL, что им лучше не пользоваться и что существуют альтернативные структуры данных deque&lt;bool&gt; и bitset, почти всегда способные заменить vector&lt;bool&gt;.</p>
</section>
</section>
<section>
<title>
<p>Ассоциативные контейнеры</p>
</title>
<section>
<p>Ассоциативные контейнеры по некоторым характеристикам схожи с последовательными контейнерами, однако между этими категориями существует ряд принципиальных различий. Так, содержимое ассоциативных контейнеров автоматически сортируется; анализ содержимого производится по критерию эквивалентности, а не равенства; контейнеры <code>set</code> и <code>map</code> не могут содержать дубликатов, а контейнеры <code>map</code> и <code>multimap</code> обычно игнорируют половину данных в каждом из содержащихся в них объектов. Да, ассоциативные контейнеры являются контейнерами, но они определенно выделяются в самостоятельную категорию.</p>
<p>В этой главе мы рассмотрим основное понятие эквивалентности; проанализируем важное ограничение, установленное для функций сравнения; познакомимся с пользовательскими функциями сравнения для ассоциативных контейнеров указателей; обсудим официальные и практические аспекты неизменности ключа, а также пути повышения эффективности ассоциативных контейнеров.</p>
<p>В STL отсутствуют контейнеры на базе хэш-таблиц, поэтому глава завершается примерами двух распространенных (хотя и нестандартных) реализаций. Несмотря на отсутствие хэш-таблиц в STL, вам не придется реализовывать их самостоятельно или обходиться другими контейнерами. Существует немало готовых качественных реализаций.</p>
<p>Задача сравнения объектов возникает в STL очень часто. Например, если функция find ищет в интервале первый объект с заданным значением, она должна уметь сравнивать два объекта и узнавать, совпадают ли их значения. При попытке включения нового элемента в множество функция set:: insert должна проверить, не существует ли данное значение в контейнере.</p>
</section>
<section>
<title>
<p>Совет 19. Помните о различиях между равенством и эквивалентностью</p>
</title>
<p>Алгоритм <strong>find </strong>и функция <strong>set::insert </strong>являются типичными представителями семейства функций, проверяющих совпадение двух величин, однако делают это они по-разному. Для <strong>find </strong>совпадением считается <emphasis><strong>равенство </strong></emphasis>двух величин, проверяемое оператором =. Функция <strong>set:: insert </strong>проверяет отношение <emphasis><strong>эквивалентности, </strong></emphasis>обычно основанное на операторе &lt; Таким образом, по одному определению два объекта могут иметь одинаковые значения, тогда как по другому определению они будут различаться. Отсюда следует, что для эффективного использования STL необходимо понимать различия между равенством и эквивалентностью.</p>
<p>Формальное определение равенства основано на использовании оператора =. Если результат выражения х=у равен <strong>true, </strong>значит, х и у имеют одинаковые значения, а если <strong>false </strong>— разные. В целом определение весьма прямолинейное, хотя следует помнить о том, что из равенства значений не следует равенство всех полей данных. Предположим, класс <strong>Widget </strong>хранит внутренние данные о времени последнего обращения:</p>
<p><code>class Widget {</code></p>
<p><code>public:</code></p>
<p><code>private:</code></p>
<p><code>	TimeStamp lastAccessed;</code></p>
<p><code>};</code></p>
<p>Для класса <strong>Widget </strong>можно определить оператор ==, игнорирующий значение этого поля:</p>
<p><code>bool operator=(const Widgets Ihs, const Widgets rhs) {</code></p>
<p><code>// Поле lastAccessed игнорируется</code></p>
<p><code>}</code></p>
<p>В этом случае два объекта <strong>Widget </strong>будут считаться равными даже в том случае, если их поля <code>lastAccessed</code> содержат разные значения.</p>
<p>Эквивалентность основана на относительном порядке значений объектов в отсортированном интервале. Проще всего рассматривать ее в контексте порядка сортировки, являющегося частью любого стандартного ассоциативного контейнера (то есть <strong>set, multiset, map </strong>и <strong>multimap). </strong>Два объекта х и у считаются эквивалентными по отношению к порядку сортировки, используемому ассоциативным контейнером с, если ни один из них не предшествует другому в порядке сортировки с. На первый взгляд такая формулировка кажется запутанной, но на практике все просто. Возьмем контейнер <strong>set&lt;Widget&gt; s. </strong>Два объекта <strong>Widge</strong><emphasis><strong>t, </strong></emphasis><strong>w1 </strong>и w2, имеют эквивалентные значения по отношению к <strong>s, </strong>если ни один из них не предшествует другому в порядке сортировки <strong>s. </strong>Стандартная функция сравнения для <strong>set&lt;Widge</strong><emphasis><strong>t&gt;</strong><strong> — </strong></emphasis>less<strong>&lt;Widget&gt; </strong>— по умолчанию просто вызывает <strong>operator&lt; </strong>для объектов <strong>Widge</strong><emphasis><strong>t, </strong></emphasis>поэтому wl и w2 будут иметь значения, эквивалентные по отношению к <strong>operator&lt; </strong>если истинно следующее выражение:</p>
<p><code>!(w1&lt;w2)	// Условие w1 &lt; w2 ложно</code></p>
<p><code>&amp;&amp;	// и</code></p>
<p><code>!(w2&lt;w1)// Условие w2 &lt; w1 ложно</code></p>
<p>Все вполне логично: два значения эквивалентны (по отношению к некоторому критерию упорядочения), если ни одно из них не предшествует другому в соответствии с данным критерием.</p>
<p>В общем случае функцией сравнения для ассоциативного контейнера является не оператор &lt; или даже less, а пользовательский предикат (см. совет 39). Каждый стандартный ассоциативный контейнер предоставляет свой предикат сортировки через функцию <code>key_comp</code>, поэтому два объекта х и у имеют эквивалентные значения по отношению к критерию сортировки ассоциативного контейнера с, если выполняется следующее условие:</p>
<p><code>!c.key_comp()(x.y) &amp;&amp; !c.key_comp()(y,x) // х не предшествует у</code></p>
<p><code>// в порядке сортировки с, </code></p>
<p><code>// а у не предшествует х</code></p>
<p>Выражение <code>!c.key_comp()(x,y)</code> выглядит устрашающе, но стоит понять, что <code>c.key_comp()</code> возвращает функцию (или объект функции), как все затруднения исчезают. Перед нами простой вызов функции (или объекта функции), возвращаемой <code>key_comp</code>, которой передаются аргументы х и у. Затем вычисляется логическое отрицание результата. Функция <code>с.keycomp ()(х, у)</code> возвращает <code>true</code> лишь в том случае, если х предшествует у в порядке сортировки, поэтому выражение <code>!с.key_comp()(х, у)</code> истинно только в том случае, если х <emphasis><strong>не предшествует </strong></emphasis>у в порядке сортировки с.</p>
<p>Чтобы вы лучше осознали принципиальный характер различий между равенством и эквивалентностью, рассмотрим пример — контейнер set&lt;string&gt; без учета регистра символов, то есть контейнер set&lt;string&gt;, в котором функция сравнения игнорирует регистр символов в строках. С точки зрения такой функции строки «STL» и «stL» эквивалентны. Пример реализации функции ciStringCompare, игнорирующей регистр символов, приведен в совете 35, однако set требуется <emphasis><strong>тип </strong></emphasis>функции сравнения, а не сама функция. Чтобы заполнить этот пробел, мы пишем класс функтора с оператором (), вызывающим ciStringCompare:</p>
<p><code>struct CiStringCompare:// Класс сравнения строк</code></p>
<p><code>public// без учета регистра символов;</code></p>
<p><code>binary_function&lt;string,string,bool&gt;{ // описание базового класса</code></p>
<p><code>// приведено в совете 40</code></p>
<p><code>bool operator() (const string&amp; lhs,</code></p>
<p><code>const string&amp; rhs) const</code></p>
<p><code>   {</code></p>
<p><code>      return ciStringCompare(lhs,rhs); // Реализация ciStringCompare</code></p>
<p><code>      // приведена в совете 35</code></p>
<p><code>   }</code></p>
<p><code>};</code></p>
<p>При наличии CiStringCompare контейнер set&lt;string&gt;, игнорирующий регистр символов, создается очень просто:</p>
<p><code>set&lt;string.CIStringCompare&gt; ciss:</code></p>
<p>Теперь при вставке строк «Persephone» и «persephone» в множество будет включена только первая строка, поскольку вторая считается эквивалентной:</p>
<p><code>ciss.insert("Persephone"); // В множество включается новый элемент</code></p>
<p><code>ciss.insert("persephone"); // Эквивалентный элемент не включается</code></p>
<p>Если теперь провести поиск строки «persephone» функцией set::find, результат будет успешным:</p>
<p><code>if(ciss.find("persephone")!=ciss.end())... // Элемент найден</code></p>
<p>Но если воспользоваться внешним алгоритмом find, поиск завершается неудачей:</p>
<p><code>if(find(ciss.begin(),ciss.end(),</code></p>
<p><code>"persephone")!=ciss.end())... // Элемент отсутствует</code></p>
<p>Дело в том, что строка «persephone» <emphasis><strong>эквивалентна </strong></emphasis>«Persephone» (по отношению к функтору сравнения CIStringCompare), но <emphasis><strong>не равна </strong></emphasis>ей (поскольку string ("persephone") !=string( "Persephone")). Приведенный пример поясняет одну из причин, по которой в соответствии с советом 44 рекомендуется использовать функции контейнеров (set:: find) вместо их внешних аналогов (find).</p>
<p>Возникает вопрос — почему же в работе стандартных ассоциативных контейнеров используется понятие эквивалентности, а не равенства? Ведь большинству программистов равенство кажется интуитивно более понятным, чем эквивалентность (в противном случае данный совет был бы лишним). На первый взгляд ответ кажется простым, но чем дольше размышляешь над этим вопросом, тем менее очевидным он становится.</p>
<p>Стандартные ассоциативные контейнеры сортируются, поэтому каждый контейнер должен иметь функцию сравнения (по умолчанию less), определяющую порядок сортировки. Эквивалентность определяется в контексте функции сравнения, поэтому клиентам стандартных ассоциативных контейнеров остается лишь задать единственную функцию сравнения. Если бы ассоциативные контейнеры при сравнении объектов использовали критерий равенства, то каждому ассоциативному контейнеру, помимо используемой при сортировке функции сравнения, пришлось бы определять вторую функцию для проверки равенства. Вероятно, по умолчанию функция сравнения вызывала бы equal_to, но интересно заметить, что функция equal_to в STL не используется в качестве стандартной функции сравнения. Когда в STL возникает необходимость проверки равенства, по умолчанию просто вызывается оператор =. В частности, именно так поступает внешний алгоритм find.</p>
<p>Допустим, у нас имеется контейнер set2CF, построенный по образцу set — «set с двумя функциями сравнения». Первая функция сравнения определяет порядок сортировки элементов множества, а вторая используется для проверки равенства. А теперь рассмотрим следующее объявление:</p>
<p><code>set2CF&lt;string.CIStringCompare,equal_to&lt;string&gt; &gt; s;</code></p>
<p>Контейнер s производит внутреннюю сортировку строк без учета регистра, но с использованием интуитивного критерия равенства: две строки считаются равными при совпадении их содержимого. Предположим, в s вставляются два варианта написания строки «Persephone»:</p>
<p><code>s.insert("Persephone");</code></p>
<p><code>s.insert("persephone");</code></p>
<p>Как поступить в этом случае? Если контейнер поймет, что "Persephone" != "persephone", и вставит обе строки в s, в каком порядке они должны следовать?</p>
<p>Напомню, что функция сортировки эти строки не различает. Следует ли вставить строки в произвольном порядке, добровольно отказавшись от детерминированного порядка перебора содержимого контейнера? Недетерминированный порядок перебора уже присущ ассоциативным контейнерам multiset и multimap, поскольку Стандарт не устанавливает никаких ограничений на относительный порядок следования эквивалентных значений (multiset) или ключей (multimap). Или нам следует настоять на детерминированном порядке содержимого s и проигнорировать вторую попытку вставки (для строки «persephone»)? А если будет выбран этот вариант, что произойдет при выполнении следующей команды:</p>
<p><code>if (s.find("persephone") != s.end())... // Каким будет результат проверки?</code></p>
<p>Функция find использует проверку равенства, но если проигнорировать второй вызов insert для сохранения детерминированного порядка элементов s, проверка даст отрицательный результат — хотя строка «persephone» была отвергнута как дубликат!</p>
<p>Мораль: используя одну функцию сравнения и принимая решение о «совпадении» двух значений на основании их эквивалентности, мы избегаем многочисленных затруднений, возникающих при использовании двух функций сравнения. Поначалу такой подход выглядит несколько странно (особенно когда вы видите, что внутренняя и внешняя версии find возвращают разные результаты), но в перспективе он избавляет от всевозможных затруднений, возникающих при смешанном использовании равенства и эквивалентности в стандартных ассоциативных контейнерах.</p>
<p>Но стоит отойти от <emphasis><strong>сортированных </strong></emphasis>ассоциативных контейнеров, как ситуация изменяется, и проблему равенства и эквивалентности приходится решать заново. Существуют две общепринятые реализации для нестандартных (но широко распространенных) ассоциативных контейнеров на базе хэш-таблиц. Одна реализация основана на равенстве, а другая — на эквивалентности. В совете 25 приводится дополнительная информация об этих контейнерах и тех принципак, на которых они основаны.</p>
</section>
<section>
<title>
<p>Совет 20. Определите тип сравнения для ассоциативного контейнера, содержащего указатели</p>
</title>
<p>Предположим, у нас имеется контейнер set, содержащий указатели string*, и мы пытаемся включить в него несколько новых элементов:</p>
<p><code>set&lt;string*&gt; ssp;	// ssp = "set of string ptrs"</code></p>
<p><code>ssp.insert(new string("Anteater"));</code></p>
<p><code>ssp.insert(new string("Wombat"));</code></p>
<p><code>ssp.insert(new string("Lemur"));</code></p>
<p><code>ssp.insert(new string("Penguin"));</code></p>
<p>Следующий фрагмент выводит содержимое set. Предполагается, что строки будут выведены в алфавитном порядке — ведь содержимое контейнеров set автоматически сортируется!</p>
<p><code>for (set&lt;string*&gt;::const_iterator i = ssp.begin(); // Предполагаемый</code></p>
<p><code>	i!=ssp.end();</code></p>
<p><code>	++i)</code></p>
<p><code>cout &lt;&lt;*i &lt;&lt; endl;</code></p>
<p>Однако на практике ничего похожего не происходит. Вместо строк выводятся четыре шестнадцатеричных числа — значения указателей. Поскольку в контейнере set хранятся указатели, *i является не строкой, а <emphasis><strong>указателем </strong></emphasis>на строку. Пусть этот урок напоминает, чтобы вы следовали рекомендациям совета 43 и избегали написания собственных циклов. Использование алгоритма сору:</p>
<p><code>copy(ssp.begin(),ssp.end(),// Скопировать строки.</code></p>
<p><code>ostream_iterator&lt;string&gt;(cout,"\n")); //содержащиеся в ssp. в cout</code></p>
<p><code>//(не компилируется!)</code></p>
<p>не только делает программу более компактной, но и помогает быстрее обнаружить ошибку, поскольку вызов сору не компилируется. Итератор ostream_iterator должен знать тип выводимого объекта, поэтому когда компилятор обнаруживает расхождение между заданным в параметре шаблона типом string и типом объекта, хранящегося в ssp (string*), он выдает ошибку. Еще один довод в пользу сильной типизации...</p>
<p>Если заменить *i в цикле на **i, <emphasis><strong>возможно, </strong></emphasis>вы получите нужный результат — но скорее всего, этого не произойдет. Да, строки будут выведены, но вероятность их следования в алфавитном порядке равна всего 1 /24. Контейнер ssp хранит свои элементы в отсортированном виде, однако он содержит указатели, поэтому сортироваться будут <emphasis><strong>значения указателей, </strong></emphasis>а не строки. Существует 24 возможных перестановки для четырех указателей, то есть 24 разных последовательности, из которых лишь одна отсортирована в алфавитном порядке<a l:href="#n2" type="note">[2]</a>.</p>
<p>Подходя к решению этой проблемы, нелишне вспомнить, что объявление</p>
<p><code>set&lt;string*&gt; ssp;</code></p>
<p>представляет собой сокращенную запись для объявления</p>
<p><code>set&lt;string*.less&lt;string*&gt; &gt; ssp;</code></p>
<p>Строго говоря, это сокращенная запись для объявления</p>
<p><code>set&lt;string*.less&lt;string*&gt;.allocator&lt;string*&gt; &gt; ssp;</code></p>
<p>но в контексте данного совета распределители памяти несущественны.</p>
<p>Если вы хотите сохранить указатели string* в контейнере set так, чтобы их порядок определялся значениями строк, стандартный функтор сравнения less&lt;string*&gt; вам не подойдет. Вместо этого необходимо написать собственный функтор сравнения, который получает указатели string* и упорядочивает их по содержимому строк, на которые они ссылаются. Пример:</p>
<empty-line/>
<p><code>struct StringPtrLess:</code></p>
<p><code>public binary_function&lt;const string*,	// Базовый класс</code></p>
<p><code>		const string*,	// описан в совете 40</code></p>
<p><code>		bool&gt; {</code></p>
<p><code>		bool operator() (const string *ps1, const string *ps2) const </code></p>
<p><code>		{</code></p>
<p><code>			return *ps1&lt;*ps2:</code></p>
<p><code>		}</code></p>
<p><code>};</code>.</p>
<p>После этого <strong>StringPtrLess </strong>используется в качестве типа критерия сравнения ssp:</p>
<p><code>typedef set&lt;string*, StringPtrLess&gt; StringPtrSet;</code></p>
<p><code>StringPtrSet ssp;	// Создать множество с объектами string</code></p>
<p><code>// и порядком сортировки, определяемым </code></p>
<p><code>// критерием StringPtrLess </code></p>
<p><code>// Вставить те же четыре строки</code></p>
<p>Теперь приведенный выше цикл будет работать именно так, как предполагалось (при условии, что ошибка была исправлена и вместо <strong>*i </strong>используется **i).</p>
<p><code>for(StringPtrSet::const _iterator i = ssp.begin();</code></p>
<p><code>	i != ssp.end();// Порядок вывода:</code></p>
<p><code>	++i)		// "Anteater", "Lemur",</code></p>
<p><code>cout«**i«endl;	// "Pengun". "Wombat"</code></p>
<p>Если вы предпочитаете использовать алгоритм, напишите функцию, которая разыменовывает указатели <strong>string* </strong>перед выводом, а затем используйте ее в сочетании с <strong>for_each:</strong></p>
<p><code>void print(const string *ps)// Вывести в cout объект.</code></p>
<p><code>{// на который ссылается ps</code></p>
<p><code>	cout <strong>«*ps </strong>« endl;</code></p>
<p><code>}</code></p>
<p><code>for_each(ssp.begin(),ssp.end(),print); // Вызвать print для каждого</code></p>
<p><code>// элемента ssp</code></p>
<p>Существует более изощренное решение — обобщенный функтор разыменования, используемый с <strong>transform </strong>и <strong>ostream_iterator:</strong></p>
<p><code>// Функтор получает Т* и возвращает const Т&amp;</code></p>
<p><code>struct Dereference{</code></p>
<p><code>   template&lt;typename T&gt;</code></p>
<p><code>   const <strong>T&amp; </strong>operator() (const <strong>T* </strong>ptr) const</code></p>
<p><code>   {</code></p>
<p><code>	   return *ptr;</code></p>
<p><code>   }</code></p>
<p><code>};</code></p>
<p><code>transform(ssp.begin(),ssp.end(),// "Преобразовать" каждый</code></p>
<p><code>	ostream.iterator&lt;string&gt;(cout,"\n"). // элемент ssp посредством</code></p>
<p><code>	Dereference());// разыменования и записать</code></p>
<p><code>	// результаты в cout</code></p>
<p>Впрочем, замена циклов алгоритмами будет подробно рассматриваться позднее, в совете 43. А сейчас речь идет о том, что при создании стандартного ассоциативного контейнера указателей следует помнить: содержимое контейнера будет сортироваться по значениям указателей. Вряд ли такой порядок сортировки вас устроит, поэтому почти всегда определяются классы-функторы, используемые в качестве типов сравнения.</p>
<p>Обратите внимание на термин <emphasis><strong>«тип </strong></emphasis>сравнения». Возможно, вас интересует, зачем возиться с созданием функтора вместо того, чтобы просто написать функцию сравнения для контейнера set? Например, так:</p>
<p><code>bool stringPtrLess(const string* psl, // Предполагаемая функция сравнения</code></p>
<p><code>const string* ps2) // для указателей string*. </code></p>
<p><code>{	// сортируемых по содержимому строки</code></p>
<p><code>   return *psl&lt;*ps2:</code></p>
<p><code>}</code></p>
<p><code>set&lt;string.stringPtrLess&gt; ssp; // Попытка использования stringPtrLess</code></p>
<p><code>	// в качестве функции сравнения ssp.</code></p>
<p><code>	// Не компилируется!!!</code></p>
<p>Проблема заключается в том, что каждый из трех параметров шаблона set должен быть <emphasis><strong>типом. </strong></emphasis>К сожалению, stringPtrLess — не тип, а функция, поэтому попытка задать stringPtrLess в качестве функции сравнения set не компилируется. Контейнеру set не нужна функция; ему нужен тип, на основании которого можно <emphasis><strong>создать </strong></emphasis>функцию.</p>
<p>Каждый раз, когда вы создаете ассоциативный контейнер указателей, помните о том, что вам, возможно, придется задать тип сравнения контейнера. В большинстве случаев тип сравнения сводится к разыменованию указателя и сравнению объектов, как это сделано в приведенном выше примере StringPtrLess. Шаблон для таких функторов сравнения стоит держать под рукой. Пример:</p>
<p><code>struct DereferenceLess {</code></p>
<p><code>template &lt;typename PtrType&gt;</code></p>
<p><code>bool operator()(PtrType pTl,	// Параметры передаются по значению.</code></p>
<p><code>		PtrType рТ2) const 	// поскольку они должны быть </code></p>
<p><code>	{		// указателями (или по крайней мере</code></p>
<p><code>	   return *рТ1&lt;*рТ2:// вести себя, как указатели)</code></p>
<p><code>	}</code></p>
<p><code>};</code></p>
<p>Данный шаблон снимает необходимость в написании таких классов, как StringPtrLess, поскольку вместо них можно использовать DereferenceLess:</p>
<p><code>set&lt;string<strong>*.DereferenceLess&gt; </strong>ssp; // Ведет себя так же. как</code></p>
<p><code>// set&lt;string*,stringPtrLess&gt;</code></p>
<p>И последнее замечание. Данный совет посвящен ассоциативным контейнерам указателей, но он в равной степени относится и к контейнерам объектов, которые <emphasis><strong>ведут себя </strong></emphasis>как указатели (например, умные указатели и итераторы). Если у вас имеется ассоциативный контейнер умных указателей или итераторов, подумайте, не стоит ли задать тип сравнения и для него. К счастью, решение, приведенное для указателей, работает и для объектов-аналогов. Если определение <code>DereferenceLess</code> подходит в качестве типа сравнения для ассоциативного контейнера Т*, оно с большой вероятностью подойдет и для контейнеров итераторов и умных указателей на объекты Т.</p>
</section>
<section>
<title>
<p>Совет 21. Следите за тем, чтобы функции сравнения возвращали false в случае равенства</p>
</title>
<p>Сейчас я покажу вам нечто любопытное. Создайте контейнер set с типом сравнения less_equal и вставьте в него число 10:</p>
<p><code>set&lt;int,less_equal&lt;int&gt; &gt; s; // Контейнер s сортируется по критерию "&lt;="</code></p>
<p><code>s.insert(10);	// Вставка числа 10</code></p>
<p>Теперь попробуйте вставить число 10 повторно:</p>
<p><code>s.insert(10);</code></p>
<p>При этом вызове insert контейнер должен выяснить, присутствует ли в нем число 10. Мы знаем, что такое число уже есть, но контейнер глуп как пробка и все проверяет лично. Чтобы вам было проще понять, что при этом происходит, назовем первоначально вставленный экземпляр 10<sub>А</sub>, а новый экземпляр — 10<sub>в</sub>.</p>
<p>Контейнер перебирает свои внутренние структуры данных и ищет место для вставки 10<sub>в</sub>. В итоге ему придется проверить 10<sub>А</sub> и сравнить его с 10<sub>в</sub>. Для ассоциативного контейнера «сравнение» сводится к проверке эквивалентности (см. совет 19), поэтому контейнер проверяет эквивалентность объектов 10<sub>А</sub> и 10<sub>в</sub>. Естественно, при этой проверке используется функция сравнения контейнера se<emphasis><strong>t; </strong></emphasis>в нашем примере это функция operator&lt;=, поскольку мы задали функцию сравнения less_equal, a less_equal означает operator&lt;=. Затем контейнер проверяет истинность следующего выражения:</p>
<p><code>!(10a&lt;=10b)&amp;&amp;!(10b&lt;=10a) // Проверка эквивалентности 10<sub>A</sub> и 10<sub>в</sub></code></p>
<p>Оба значения, 10<sub>А</sub> и 10<sub>в</sub>, равны 10, поэтому условие 10<sub>А</sub>&lt;=10<sub>В</sub> заведомо истинно. Аналогично истинно и условие 10<sub>В</sub>&lt;=10<sub>А</sub>. Приведенное выше выражение упрощается до <emphasis><strong>!(t</strong></emphasis>rue) <emphasis><strong>&amp;&amp;!(t</strong></emphasis>rue), то есть false &amp;&amp; false — результат равен false. Другими словами, контейнер приходит к выводу, что 10<sub>А</sub> и 10<sub>в</sub> <emphasis><strong>не эквивалентны, </strong></emphasis>и вставляет 10<sub>в</sub> в контейнер наряду с 10<sub>А</sub>. С технической точки зрения эта попытка приводит к непредсказуемым последствиям, но на практике в контейнере set появляются два экземпляра значения 10, а это означает утрату одного из важнейших свойств set<emphasis><strong>. </strong></emphasis>Передача типа сравнения less_equal привела к порче контейнера! Более того, <emphasis><strong>любая </strong></emphasis>функция сравнения, которая возвращает true для равных значений, приведет к тем же последствиям. Равные значения по определению <emphasis><strong>не </strong></emphasis>эквивалентны! Здорово, не правда ли?</p>
<p>Мораль: всегда следите за тем, чтобы функции сравнения для ассоциативных контейнеров возвращали false для равных значений. Будьте внимательны, поскольку это ограничение очень легко упустить из виду.</p>
<p>Например, в совете 20 рассказано о том, как написать функцию сравнения для контейнеров указателей string* обеспечивающую автоматическую сортировку содержимого контейнера по значениям строк, а не указателей. Приведенная функция сравнения сортирует строки по возрастанию, но давайте предположим, что вам понадобилась функция для сортировки по убыванию. Естественно, вы возьмете существующий код и отредактируете его. Но если не проявить достаточной осторожности, у вас может получиться следующий результат (изменения выделены жирным шрифтом):</p>
<p><code>struct StringPtr<strong>Greater</strong>: </code></p>
<p><code>public binary_function&lt;const string*, // Жирным шрифтом выделены</code></p>
<p><code>		const string*, // изменения, внесенные в код bool&gt; {// из совета 20.</code></p>
<p><code>		// Внимание - приведенное решение </code></p>
<p><code>		// не работает!</code></p>
<p><code>bool operator()(const string *psl. const string *ps2) const </code></p>
<p><code>	{</code></p>
<p><code>	   return <strong>!</strong>(*psl&lt;*ps2); // Простое логическое отрицание</code></p>
<p><code>	}// старого условия не работает!</code></p>
<p><code>};</code></p>
<p>Идея заключается в том, чтобы изменить порядок сортировки логическим отрицанием условия в функции сравнения. К сожалению, отрицанием операции «&lt;» является не «&gt;», а «&gt;=», а мы выяснили, что операция «&gt;=», возвращающая true для равных значений, не подходит для функции сравнения в ассоциативных контейнерах.</p>
<p>Правильный тип сравнения должен выглядеть так:</p>
<p><code>struct StringPtrGreater:	// Правильный тип сравнения</code></p>
<p><code>public binary_function&lt;const string*, // для ассоциативных контейнеров</code></p>
<p><code>		const string*,</code></p>
<p><code>		 bool&gt; {</code></p>
<p><code>		bool operator() (const string *psl, const string *ps2) const {</code></p>
<p><code>			return *ps2&lt;*psl:// Поменять местами операнды</code></p>
<p><code>		}</code></p>
<p><code>}:</code></p>
<p>Чтобы не попасть в ловушку, достаточно запомнить, что возвращаемое значение функции сравнения указывает, должно ли одно значение предшествовать другому в порядке сортировки, определяемом этой функцией. Равные значения никогда не предшествуют друг другу, поэтому функция сравнения всегда должна возвращать для них false.</p>
<p>Я знаю, о чем вы думаете. «Конечно, это имеет смысл для set и map, поскольку эти контейнеры не могут содержать дубликатов. А как насчет multiset и multimap? Раз эти контейнеры могут содержать дубликаты, так ли важно, что два объекта с одинаковыми значениями окажутся не эквивалентными? Сохраним оба, для этого и нужны mult-контейнеры. Верно?»</p>
<p>Нет, неверно. Давайте вернемся к исходному примеру, но на этот раз воспользуемся контейнером multiset:</p>
<p><code>multiset&lt;int.less_equal&lt;int&gt; &gt; s; // s сортируется по критерию "&lt;="</code></p>
<p><code>s.insert(10):// Вставка числа 10<sub>А</sub></code></p>
<p><code>s.insert(10);// Вставка числа 10<sub>в</sub></code></p>
<p>Теперь s содержит два экземпляра числа 10, и было бы логично предположить, что при вызове equal _range мы получим пару итераторов, описывающих интервал с обеими копиями. Однако это невозможно. Функция equal_range, несмотря на свое название, определяет интервал не равных, а <emphasis><strong>эквивалентных </strong></emphasis>значений. В нашем примере функция сравнения s говорит, что 10<sub>А</sub> и 10<sub>в</sub> не эквивалентны, поэтому они не могут оказаться в интервале, определяемом функцией equal range.</p>
<p>Ну что, убедились? Функция сравнения всегда должна возвращать false для равных величин, в противном случае нарушается работа всех стандартных ассоциативных контейнеров (независимо от того, могут они содержать дубликаты или нет).</p>
<p>Строго говоря, функции сравнения, используемые для сортировки ассоциативных контейнеров, должны определять для сравниваемых объектов порядок строгой квазиупорядоченности (strict weak ordering); аналогичное ограничение действует и для функций сравнения, передаваемых алгоритмам, — таким, как sort (см. совет 31). Если вас заинтересуют подробности того, что понимается под строгой квазиупорядоченностью, информацию можно найти во многих серьезных руководствах по STL, в том числе «The С++ Standard Library» [3], «Generic Programming аnd the STL» [4] и на web-сайте SGI, посвященном STL [21]. Особых откровений не ждите, но одно из требований строгой квазиупорядоченности относится непосредственно к данному совету. Требование заключается в следующем: функция, определяющая строгую квазиупорядоченность, должна возвращать false при получении двух копий одного значения.</p>
</section>
<section>
<title>
<p>Совет 22. Избегайте изменения ключа «на месте» в контейнерах set и multiset</p>
</title>
<p>Понять смысл этого совета нетрудно. Контейнеры set/multiset, как и все стандартные ассоциативные контейнеры, хранят свои элементы в отсортированном порядке, и правильное поведение этих контейнеров зависит от сохранения этого порядка. Если изменить значение элемента в ассоциативном контейнере (например заменить 10 на 1000), новое значение окажется в неправильной позиции, что нарушит порядок сортировки элементов в контейнере.</p>
<p>Сказанное прежде всего касается контейнеров map и multimap, поскольку программы, пытающиеся изменить значение ключа в этих контейнерах, не будут компилироваться:</p>
<p><code>map&lt;int.string&gt; m;</code></p>
<p><code>m.begin()-&gt;first = 10:// Ошибка! Изменение ключей</code></p>
<p><code>// в контейнере map запрещено</code></p>
<p><code>multimap&lt;int.string&gt; mm;</code></p>
<p><code>mm.begin()-&gt;first = 20;// Ошибка! Изменение ключей</code></p>
<p><code>// в контейнере multimap запрещено</code></p>
<p>Дело в том, что элементы объекта типа map&lt;K,V&gt; или multimap&lt;K,V&gt; относятся к типу pair&lt;const К, V&gt;. Ключ относится к типу const К и поэтому не может изменяться. Впрочем, его все же можно изменить с применением const_cast, как показано ниже. Хотите — верьте, хотите — нет, но иногда это даже <emphasis><strong>нужно.</strong></emphasis></p>
<p>Обратите внимание: в заголовке этого совета ничего не сказано о контейнерах пир и multimap. Для этого есть веские причины. Как показывает предыдущий пример, модификация ключа «на месте» невозможна для map и multimap (без применения преобразования const_cast), но может быть допустима для set и multiset. Для объектов типа set&lt;T&gt; и multiset&lt;T&gt; в контейнере хранятся элементы типа Т, а не const Т. Следовательно, элементы контейнеров set и multiset можно изменять в любое время, и преобразование const_cast для этого не требуется (вообще говоря, дело обстоит не так просто, но не будем забегать вперед).</p>
<p>Сначала выясним, почему элементы set и multiset не имеют атрибута const. Допустим, у нас имеется класс Emplоуее:</p>
<p><code>class Employee { </code></p>
<p><code>public:</code></p>
<p><code>const string&amp; name() const;// Возвращает имя работника</code></p>
<p><code>void setName(const string&amp; name); // Задает имя работника</code></p>
<p><code>const string&amp; title() const;// Возвращает должность</code></p>
<p><code>void setTitle(const string&amp; title); // Задает должность</code></p>
<p><code>int idNumber() const;// Возвращает код работника</code></p>
<p><code>}</code></p>
<p>Объект содержит разнообразные сведения о работнике. Каждому работнику назначается уникальный код, возвращаемый функцией idNumber. При создании контейнера set с объектами Emplоуее было бы вполне разумно упорядочить его по кодам работников:</p>
<p><code>struct IDNumberLess:</code></p>
<p><code>public binary_function&lt;Employee.Employee,bool&gt; { // См. совет 40</code></p>
<p><code>bool operator() (const Employees Ihs,</code></p>
<p><code>	const Employees rhs) const</code></p>
<p><code>   {</code></p>
<p><code>	   return lhs.idNumber() &lt; rhs. IdNumber();</code></p>
<p><code>   }</code></p>
<p><code>}</code></p>
<p><code>typedef set&lt;Employee.IDNumberLess&gt; EmplIDSet;</code></p>
<p><code>EmplIDSet se;// Контейнер set объектов</code></p>
<p><code>// Employee, упорядоченных </code></p>
<p><code>// по коду</code></p>
<p>С практической точки зрения код работника является <emphasis><strong>ключом </strong></emphasis>для элементов данного множества, а остальные данные вторичны. Учитывая это обстоятельство, ничто не мешает перевести работника на более интересную должность. Пример:</p>
<p><code>Employee selectedID;// Объект работника с заданным кодом</code></p>
<p><code>EmpIDSet::iterator =se.find(selectedlD);</code></p>
<p><code>if (i!=se.end()){</code></p>
<p><code>	i-&gt;setTitle("Corporate Dety"); // Изменить должность</code></p>
<p><code>}</code></p>
<p>Поскольку мы всего лишь изменяем вторичный атрибут данных, не влияющий на порядок сортировки набора, этот фрагмент не приведет к порче данных, и он вполне допустим.</p>
<p>Спрашивается, почему нельзя применить ту же логику к ключам контейнеров map и multimap? Почему бы не создать контейнер map, ассоциирующий работников со страной, в которой они живут; контейнер с функцией сравнения IDNumberLess, как в предыдущем примере? И почему бы в таком контейнере не изменить должность без изменения кода работника, как в предыдущем примере?</p>
<p>Откровенно говоря, мне это кажется вполне логичным, однако мое личное мнение в данном случае несущественно. Важно то, что Комитет по стандартизации решил, что ключи map/multimap должны быть неизменными (const), а значения set/ multiset — не должны.</p>
<p>Значения в контейнерах set/multiset не являются неизменными, поэтому попытки их изменения обычно нормально компилируются. Данный совет лишь напоминает вам о том, что при модификации элементов set/multiset не следует изменять ключевую часть (то есть ту часть элемента, которая влияет на порядок сортировки в контейнере). В противном случае целостность данных контейнера будет нарушена, операции с контейнером начнут приводить к непредсказуемым результатам, и все это произойдет по вашей вине. С другой стороны, это ограничение относится только к ключевым атрибутам объектов, содержащихся в контейнере. Остальные атрибуты объектов находятся в вашем полном распоряжении — изменяйте на здоровье!</p>
<p>Впрочем, не все так просто. Хотя элементы set/multiset и не являются неизменными, реализации могут предотвратить их возможную модификацию. Например, оператор* для set&lt;T&gt;: iterator может возвращать const Т&amp;, то есть результат разыменования итератора set может быть ссылкой на const-элемент контейнера! При такой реализации изменение элементов set и multiset невозможно, поскольку при любом обращении к элементу автоматически добавляется объявление const.</p>
<p>Законны ли такие реализации? Может, да, а может — нет. По этому вопросу Стандарт высказывается недостаточно четко, и в соответствии с законом Мерфи разные авторы интерпретируют его по-разному. В результате достаточно часто встречаются реализации STL, в которых следующий фрагмент компилироваться не будет (хотя ранее говорилось о том, что он успешно компилируется):</p>
<p><code>EmplIDSet se;// Контейнер set объектов</code></p>
<p><code>// Employee, упорядоченных </code></p>
<p><code>// по коду</code></p>
<p><code>Employee selectedID;	// Объект работника с заданным кодом</code></p>
<p><code>EmpIDSet::iterator=se.find(selectedID);</code></p>
<p><code>if (i!=se.end()){</code></p>
<p><code>	i-&gt;setTitle("Corporate Deity"); // Некоторые реализации STL</code></p>
<p><code>};	// выдают ошибку в этой строке</code></p>
<p>Вследствие неоднозначности стандарта и обусловленных ею различий в реализациях программы, пытающиеся модифицировать элементы контейнеров set и multiset, не переносимы.</p>
<p>Что из этого следует? К счастью, ничего особенно сложного:</p>
<p>•если переносимость вас не интересует, если вы хотите изменить значение элемента в контейнере set/multiset и ваша реализация STL это разрешает — действуйте. Помните о том, что ключевая часть элемента (то есть часть элемента, определяющая порядок сортировки элементов в контейнере) должна сохраниться без изменений;</p>
<p>•если программа должна быть переносимой, элементы контейнеров set/ multiset модифицироваться не могут (по крайней мере, без преобразования const_cast).</p>
<p>Кстати, о преобразованиях. Вы убедились в том, что изменение вторичных данных элемента set/multiset может быть вполне оправданно, поэтому я склонен показать, как это делается — а точнее, делается правильно и переносимо. Сделать это нетрудно, но при этом приходится учитывать тонкость, о которой забывают многие программисты — преобразование должно приводить к <emphasis><strong>ссылке. </strong></emphasis>В качестве примера рассмотрим вызов setTitle, который, как было показано, не компилируется в некоторых реализациях:</p>
<p><code>EmpIDSet::iterator i=se.find(selectedID);</code></p>
<p><code>if (i!=se.end()) {</code></p>
<p><code>	i-&gt;setTitle("Corporate Deity"); // Некоторые реализации STL</code></p>
<p><code>}// выдают ошибку в этой строке,</code></p>
<p><code>// поскольку *i имеет атрибут const</code></p>
<p>Чтобы этот фрагмент нормально компилировался и работал, необходимо устранить константность *i. Правильный способ выглядит так:</p>
<p><code>if (i!=se.end()){// Устранить</code></p>
<p><code><strong>const_cast&lt;Empioyee&amp;&gt;(*i)</strong>.setTitle("Corporate Deity"); // константность *i</code></p>
<p><code>}</code></p>
<p>Мы берем объект, на который ссылается <emphasis><strong>i, </strong></emphasis>и сообщаем компилятору, что результат должен интерпретироваться как ссылка на (неконстантный) объект Employee, после чего вызываем setTitle для полученной ссылки. Я не буду тратить время на долгие объяснения и лучше покажу, почему альтернативное решение работает совсем не так, как можно было бы ожидать.</p>
<p>Многие программисты пытаются воспользоваться следующим кодом:</p>
<p><code>if (i!=se.end()){// Преобразовать *i</code></p>
<p><code>static_cast&lt;Employee&gt;(*i).setTitle("Corporate Deity"); // к Employee</code></p>
<p><code>}</code></p>
<p>Приведенный фрагмент эквивалентен следующему:</p>
<p><code>if (i!=se.end()){// То же самое.</code></p>
<p><code>   ((Employee)(*i)).setTitle("Corporate Deity");</code></p>
<p><code>// но с использованием </code></p>
<p><code>} // синтаксиса С</code></p>
<p>Оба фрагмента компилируются, но вследствие эквивалентности работают неправильно. На стадии выполнения объект <emphasis><strong>*i </strong></emphasis>не модифицируется, поскольку в обоих случаях результатом преобразования является временный анонимный объект — <emphasis><strong>копия *i</strong></emphasis>, и setTitle вызывается для анонимного объекта, а не для *i! Обе синтаксические формы эквивалентны следующему фрагменту:</p>
<p><code>if (i!=se.end()){</code></p>
<p><code> Employee tempCopy(*i);// Скопировать *i в tempCopy</code></p>
<p><code> tempCopy.setTitle("Corporate Deity");// Изменить tempCopy</code></p>
<p><code>}</code></p>
<p>Становится понятно, почему преобразование должно приводить именно к ссылке — тем самым мы избегаем создания нового объекта. Вместо этого результат преобразования представляет собой ссылку на <emphasis><strong>существующий </strong></emphasis>объект, на который указывает i. При вызове setTitle для объекта, обозначенного ссылкой, функция вызывается для *i, чего мы и добивались.</p>
<p>Все сказанное хорошо подходит для контейнеров set и multiset, но при переходе к map/multimap ситуация усложняется. Вспомните, что map&lt;K,V&gt; и multimap&lt;K,V&gt; содержат элементы типа pair&lt;const K,V&gt;. Объявление const означает, что первый компонент пары <emphasis><strong>определяется </strong></emphasis>как константа, а из этого следует, что любые попытки устранить его константность приводят к непредсказуемому результату. Теоретически реализация STL может записывать такие данные в область памяти, доступную только для чтения (например, в страницу виртуальной памяти, которая после исходной записи защищается вызовом системной функции), и попытки устранить их константность в лучшем случае ни к чему не приведут. Я никогда не слышал о реализациях, которые бы поступали подобным образом, но если вы стремитесь придерживаться правил, установленных в Стандарте, — <emphasis><strong>никогда </strong></emphasis>не пытайтесь устранять константность ключей в контейнерах map и multimap.</p>
<p>Несомненно, вы слышали, что подобные преобразования рискованны. Надеюсь, вы будете избегать их по мере возможности. Выполняя преобразование, вы временно отказываетесь от страховки, обеспечиваемой системой типов, а описанные проблемы дают представление о том, что может произойти при ее отсутствии.</p>
<p>Многие преобразования (включая только что рассмотренные) не являются абсолютно необходимыми. Самый безопасный и универсальный способ модификации элементов контейнера set, multiset, map или multimap состоит из пяти простых шагов.</p>
<p>1.Найдите элемент, который требуется изменить. Если вы не уверены в том, как сделать это оптимальным образом, обратитесь к рекомендациям по поводу поиска в совете 45.</p>
<p>2.Создайте копию изменяемого элемента. Помните, что для контейнеров map/ multimap первый компонент копии не должен объявляться константным — ведь именно его мы и собираемся изменить!</p>
<p>3.Удалите элемент из контейнера. Обычно для этого используется функция erase (см. совет 9).</p>
<p>4.Измените копию и присвойте значение, которое должно находиться в контейнере.</p>
<p>5.Вставьте новое значение в контейнер. Если новый элемент в порадке сортировки контейнера находится в позиции удаленного элемента или в соседней позиции, воспользуйтесь «рекомендательной» формой insert, повышающей эффективность вставки от логарифмической до постоянной сложности. В качестве рекомендации обычно используется итератор, полученный на шаге 1.</p>
<p><code>EmpIDSet: iterator i= se.find(selectedlD);</code></p>
<p><code>if (i!=se.end()) { Employee e(*i);</code></p>
<p><code>se.erase(i++):</code></p>
<p><code>// Этап 1: поиск изменяемого элемента</code></p>
<p><code>//Этап 2: копирование элемента</code></p>
<p><code>//Этап 3: удаление элемента.</code></p>
<p><code>//Увеличение итератора</code></p>
<p><code>//сохраняет его</code></p>
<p><code>//действительным (см. совет 9)</code></p>
<p><code>e.setTitle("Corporate Deity"); // Этап 4: модификация копии</code></p>
<p><code>se.insert(i,е):</code></p>
<p><code>// Этап 5: вставка нового значения.</code></p>
<p><code>//Рекомендуемая позиция совпадает</code></p>
<p><code>//с позицией исходного элемента</code></p>
<p>Итак, при изменении «на месте» элементов контейнеров set и multiset следует помнить, что за сохранение порядка сортировки отвечает программист.</p>
</section>
<section>
<title>
<p>Совет 23. Рассмотрите возможность замены ассоциативных контейнеров сортированными векторами</p>
</title>
<p>Многие программисты STL, столкнувшись с необходимостью структуры данных с быстрым поиском, немедленно выбирают стандартные ассоциативные контейнеры set, multiset, map и multimap. В этом выборе нет ничего плохого, но он не исчерпывает всех возможных вариантов. Если скорость поиска действительно важна, подумайте об использовании нестандартных хэшированных контейнеров (см. совет 25). При правильном выборе хэш-функций хэшированные контейнеры могут обеспечить поиск с постоянным временем (а при неправильном выборе хэш-функций или недостаточном размере таблиц быстродействие заметно снижается, но на практике это встречается относительно редко). Во многих случаях предполагаемое постоянное время поиска превосходит гарантированное логарифмическое время, характерное для контейнеров set, map и их multi -аналогов.</p>
<p>Даже если гарантированное логарифмическое время поиска вас устраивает, стандартные ассоциативные контейнеры не всегда являются лучшим выбором. Как ни странно, стандартные ассоциативные контейнеры по быстродействию нередко уступают банальному контейнеру vector. Чтобы эффективно использовать STL, необходимо понимать, в каких случаях vector превосходит стандартные ассоциативные контейнеры по скорости поиска.</p>
<p>Стандартные ассоциативные контейнеры обычно реализуются в виде сбалансированных бинарных деревьев. Сбалансированное бинарное дерево представляет собой структуру данных, оптимизированную для комбинированных операций вставки, удаления и поиска. Другими словами, оно предназначено для приложений, которые вставляют в контейнер несколько элементов, затем производят поиск, потом вставляют еще несколько элементов, затем что-то удаляют, снова возвращаются к удалению или вставке и т. д. Главной особенностью этой последовательности событий является чередование операций вставки, удаления и поиска. В общем случае невозможно предсказать следующую операцию, выполняемую с деревом.</p>
<p>Во многих приложениях структуры данных используются не столь непредсказуемо. Операции со структурами данных делятся на три раздельные фазы.</p>
<p>1.Подготовка. Создание структуры данных и вставка большого количества элементов. В этой фазе со структурой данных выполняются только операции вставки и удаления. Поиск выполняется редко или полностью отсутствует.</p>
<p>2.Поиск. Выборка нужных данных из структуры. В этой фазе выполняются только операции поиска. Вставка и удаление выполняются редко или полностью отсутствуют.</p>
<p>3.Реорганизация. Модификация содержимого структуры данных (возможно, со стиранием всего текущего содержимого и вставкой новых элементов). По составу выполняемых операций данная фаза эквивалентна фазе 1. После ее завершения приложение возвращается к фазе 2.</p>
<p>В приложениях, использующих эту схему работы со структурами данных, контейнер vector может обеспечить лучшие показатели (как по времени, так и по затратам памяти), чем ассоциативный контейнер. С другой стороны, выбор vector не совсем произволен — подходят только <emphasis><strong>сортированные </strong></emphasis>контейнеры vector, поскольку лишь они правильно работают с алгоритмами binary_search, lower_bound, equal_range и т. д. (совет 34). Но почему бинарный поиск через вектор (может быть, отсортированный) обеспечивает лучшее быстродействие, чем бинарный поиск через двоичное дерево? Прежде всего из-за банального принципа «размер имеет значение». Существуют и другие причины, не столь банальные, но не менее истинные, и одна из них — локализованность ссылок.</p>
<p>Начнем с размера. Допустим, нам нужен контейнер для хранения объектов Widget. Скорость поиска является важным фактором, поэтому рассматриваются два основных кандидата: ассоциативный контейнер объектов Widget и сортированный vector&lt;Widget&gt;. В первом случае почти наверняка будет использоваться сбалансированное бинарное дерево, каждый узел которого содержит не только Widget, но и указатели на левого и правого потомков и (обычно) указатель на родительский узел. Следовательно, при хранении одного объекта Widget в ассоциативном контейнере должны храниться минимум три указателя.</p>
<p>С другой стороны, при сохранении Widget в контейнере vector непроизводительные затраты отсутствуют. Конечно, контейнер vector сам по себе требует определенных затрат памяти, а в конце вектора может находиться зарезервированная память (см. совет 14), но затраты первой категории как правило невелики (обычно это три машинных слова — три указателя или два указателя с одним числом int), а пустое место при необходимости отсекается при помощи «фокуса с перестановкой» (см. совет 17). Но даже если зарезервированная память и не будет освобождена, для нашего анализа ее наличие несущественно, поскольку в процессе поиска ссылки на эту память не используются.</p>
<p>Большие структуры данных разбиваются на несколько страниц памяти, однако для хранения vector требуется меньше страниц, чем для ассоциативного контейнера. Это объясняется тем, что в vector объект Widget хранится без дополнительных затрат памяти, тогда как в ассоциативном контейнере к каждому объекту Widget прилагаются три указателя. Предположим, вы работаете в системе, где объект Widget занимает 12 байт, указатели — 4 байт, а страница памяти содержит 4096 байт. Если не обращать внимания на служебную память контейнера, vector позволяет разместить на одной странице 341 объект Widget, но в ассоциативном контейнере это количество уменьшается до 170. Следовательно, по эффективности расходования памяти vector вдвое превосходит ассоциативный контейнер. В средах с виртуальной памятью это увеличивает количество подгрузок страниц, что значительно замедляет работу с большими объемами данных.</p>
<p>В действительности я несколько оптимистично подошел к ассоциативным контейнерам — приведенное описание предполагает, что узлы бинарных деревьев сгруппированы в относительно малом наборе страниц памяти. В большинстве реализаций STL подобная группировка достигается при помощи нестандартных диспетчеров памяти, работающих поверх распределителей памяти контейнеров (см. советы 10 и И), но если реализация не следит за локализованностью ссылок, узлы могут оказаться разбросанными по всему адресному пространству. Это приведет к росту числа подгрузок страниц. Даже при использовании группирующих диспетчеров памяти в ассоциативных контейнерах обычно чаще возникают проблемы с подгрузкой страниц, поскольку узловым контейнерам, в отличие от блоковых (таких как vector), труднее обеспечить близкое расположение соседних элементов контейнера в физической памяти. Однако именно эта организация памяти сводит к минимуму подгрузку страниц при выполнении бинарного поиска.</p>
<p>Мораль: данные, хранящиеся в сортированном векторе, обычно занимают меньше памяти, чем те же данные в стандартном ассоциативном контейнере; бинарный поиск в сортированном векторе обычно происходит быстрее, чем поиск в стандартном ассоциативном контейнере (с учетом подгрузки страниц).</p>
<p>Конечно, сортированный vector обладает серьезным недостатком — он должен постоянно сохранять порядок сортировки! При вставке нового элемента все последующие элементы сдвигаются на одну позицию. Операция сдвига обходится довольно дорого и становится еще дороже при перераспределении памяти (см. совет 14), поскольку после этого обычно приходится копировать <emphasis><strong>все </strong></emphasis>элементы вектора. С другой стороны, при удалении элемента из вектора все последующие элементы сдвигаются на одну позицию к началу. Операции вставки-удаления дорого обходятся для контейнеров vector, но относительно дешевы для ассоциативных контейнеров. По этой причине сортированные контейнеры vector используются вместо ассоциативных контейнеров лишь в том случае, если вы знаете, что при использовании структуры данных операции поиска почти не смешиваются со вставкой и удалением.</p>
<p>В этом совете было много текста, но катастрофически не хватало примеров. Давайте рассмотрим базовый код использования сортированного vector вместо set:</p>
<p><code>vector&lt;Widget&gt; vw;// Альтернатива для set&lt;Widget&gt;</code></p>
<p><code>// Подготовительная фаза: много вставок, </code></p>
<p><code>// мало операций поиска</code></p>
<p><code>sort(vw.begin().vw.end()); // Конец подготовительной фазы (при эмуляции</code></p>
<p><code>// multiset можно воспользоваться </code></p>
<p><code>// алгоритмом stable_sort - см. совет 31).</code></p>
<p><code>Widget w;// Объект с искомым значением</code></p>
<p><code>// Начало фазы поиска</code></p>
<p><code>if (binary_search(vw.begin(),vw.end(),w))... // Поиск с применением</code></p>
<p><code>// binary_search</code></p>
<p><code>vector&lt;Widget&gt;::iterator i = lower_bound(vw.begin(),vw.end(),w); // Поиск с применением</code></p>
<p><code>if (i!=vw.end() &amp;&amp; !(*i&lt;w))...// lower_bound: конструкция</code></p>
<p><code>// !(*i&lt;w)) описана в совете 45</code></p>
<p><code>pair&lt;vector&lt;Widget&gt;::iterator.</code></p>
<p><code>vector&lt;Widget&gt;::iterator&gt; range = equal_range(vw.begin().vw.end(),w): // Поиск с применением if (range, first !- range, second)...// equal_range</code></p>
<p><code>// Конец фазы поиска, // начало фазы реорганизации sort(vw.begin().vw.end()):// Начало новой фазы поиска...</code></p>
<p>Как видите, все реализуется достаточно прямолинейно. Основные затруднения связаны с выбором алгоритма поиска (binary_search, lower_bound и т. д.), но в этом вам поможет совет 45.</p>
<p>При переходе от map/multimap к контейнеру vector ситуация становится более интересной, поскольку vector должен содержать объекты pair, входящие в map/ multimap. Но при объявлении объекта типа <code>map&lt;K, V&gt;</code> (или его multimap-аналога) элементы, хранящиеся в контейнере, в действительности относятся к типу <code>pair<emphasis>&lt;const </emphasis>К, V&gt;</code>. Чтобы эмулировать map или multimap на базе vector, признак константности необходимо устранить, поскольку в процессе сортировки элементы вектора перемещаются посредством присваивания, а это означает, что оба компонента пары должны допускать присваивание. Следовательно, при эмуляции <code>map&lt;K,V&gt;</code> на базе vector данные, хранящиеся в векторе, должны относиться к типу <code>pair&lt;K,V&gt;</code>, а не <code>pair&lt;const K,V&gt;</code>.</p>
<p>Содержимое map/multimap хранится в отсортированном виде, но при сортировке учитывается только ключевая составляющая элемента (первый компонент пары), поэтому при сортировке vector должно происходить то же самое. Нам придется написать собственную функцию сравнения для пар, поскольку оператор &lt; типа pair сравнивает <emphasis><strong>обе </strong></emphasis>составляющие пары.</p>
<p>Интересно заметить, что для выполнения поиска требуется вторая функция сравнения. Функция сравнения, используемая при сортировке, получает два объекта pair, но поиск выполняется только по значению ключа. С другой стороны, функция сравнения, используемая при поиске, должна получать два разнотипных объекта — объект с типом ключа (искомое значение) и pair (одна из пар, хранящихся в векторе). Но это еще не все: мы не знаем, что передается в первом аргументе — ключ или pair, поэтому в действительности для поиска необходимы две функции: одна получает ключ, а другая — объект pair. В следующем примере объединено все сказанное ранее:</p>
<p><code>typedef pair&lt;string,int&gt; Data; // Тип, хранимый в "map" в данном примере</code></p>
<p><code>class DataCompare{// Класс для функций сравнения public:</code></p>
<p><code>bool operator()(constData&amp; Ihs, //Функция сравнения</code></p>
<p><code>constData&amp; rhs) const //для сортировки</code></p>
<p><code>{</code></p>
<p><code>return keyLess(Ihs.first,rhs.first); //Определение keyLess</code></p>
<p><code>}//приведено ниже</code></p>
<p><code>bool operator()(const Data&amp; Ihs.// Функция сравнения</code></p>
<p><code>const Data::first_type&amp; k) const // для поиска (форма 1)</code></p>
<p><code>{</code></p>
<p><code>return keyLess(lhs.first,rhs.first);</code></p>
<p><code>bool operator()(const Data::first_type&amp; k.// Функция сравнения</code></p>
<p><code>	const Data&amp; rhs) const;// для поиска (форма 2)</code></p>
<p><code>{</code></p>
<p><code>	return keyLess(k.rhs.first);</code></p>
<p><code>}</code></p>
<p><code>private:// "Настоящая" функция</code></p>
<p><code>bool keyLess(const Data::first_type&amp; kl.// сравнения</code></p>
<p><code>const Data::first_type&amp; k2) const</code></p>
<p><code>{</code></p>
<p><code>return kl &lt; k2;</code></p>
<p><code>}</code></p>
<p><code>}</code></p>
<p>В данном примере предполагается, что сортированный вектор эмулирует map&lt;string,int&gt;. Перед нами практически буквальное переложение комментариев, приведенных ранее, если не считать присутствия функции keyLess, предназначенной для согласования функций operator(). Каждая функция просто сравнивает два ключа, поэтому, чтобы не программировать одни и те же действия дважды, мы производим проверку в keyLess, а функция operator() возвращает полученный результат. Конечно, этот прием упрощает сопровождение DataCompare, однако у него есть один недостаток: наличие функций operator() с разными типами параметров исключает адаптацию объектов функций (см. совет 40). С этим ничего не поделаешь.</p>
<p>Контейнер map эмулируется на базе сортированного вектора практически так же, как и контейнер set. Единственное принципиальное отличие заключается в том, что в качестве функций сравнения используются объекты DataCompare:</p>
<p><code>vector&lt;Widget&gt; vd;// Альтернатива для map&lt;string.int&gt;</code></p>
<p><code>// Подготовительная фаза: много вставок, // мало операций поиска</code></p>
<p><code>sort(vd.begin().vd.end(),DataCompare()); // Конец подготовительной фазы</code></p>
<p><code>// (при эмуляции multiset можно // воспользоваться алгоритмом // stable_sort - см. совет 31)</code></p>
<p><code>string s;// Объект с искомым значением</code></p>
<p><code>// Начало фазы поиска</code></p>
<p><code>if (binary_search(vd.begin(),vd.end(),s,DataCompare()))... // Поиск</code></p>
<p><code>// с применением binary_search</code></p>
<p><code>vector&lt;Data&gt;::iterator i = 1ower_bound(vd.begin(),vd.end().s, DataCompareO): if (i!=vd.end() &amp;&amp; <emphasis>!(i</emphasis>-&gt;first&lt;s))„.</code></p>
<p><code>//Поиск с применением</code></p>
<p><code>//lower_bound: конструкция</code></p>
<p><code>//!(i-&gt;first&lt;s)) описана</code></p>
<p><code>//в совете 45</code></p>
<p><code>pair&lt;vector&lt;Data&gt;::iterator.</code></p>
<p><code>vector&lt;Data&gt;::iterator&gt; range = equal_range(vd.begin() .vd.end() ,s. DataCompareO): if (range, first !- range, second)...</code></p>
<p><code>//Поиск с применением</code></p>
<p><code>//equal_range</code></p>
<p><code>//Конец фазы поиска,</code></p>
<p><code>//начало фазы реорганизации</code></p>
<p><code>//Начало новой фазы поиска...</code></p>
<p><code>sort(vd.begin(),vd.end(),DataCompare());</code></p>
<p>Как видите, после написания DataCompare все более или менее становится на свои места. Показанное решение часто быстрее работает и расходует меньше памяти, чем аналогичная архитектура с настоящим контейнером map — при условии, что операции со структурой данных в вашей программе делятся на фазы, описанные на с. 99. Если подобное деление на фазы не соблюдается, использование сортированного вектора вместо стандартных ассоциативных контейнеров почти всегда оборачивается напрасной тратой времени.</p>
</section>
<section>
<title>
<p>Совет 24. Тщательно выбирайте между map::operator[] и map::insert</p>
</title>
<p>Допустим, у нас имеется класс Widget с конструктором по умолчанию, а также конструктором и оператором присваивания с операндом типа double:</p>
<p><code>class Widget { </code></p>
<p><code>public:</code></p>
<p><code>	Widget();</code></p>
<p><code>	Widget(double weight);</code></p>
<p><code>	Widget&amp; operator=(double weight);</code></p>
<p><code>};</code></p>
<p>Предположим, мы хотим создать контейнер map, ассоциирующий int с Widget, и инициализировать созданное множество конкретными значениями. Все выглядит очень просто:</p>
<p><code>map&lt;int.Widget&gt; m;</code></p>
<p><code>m[1]=1.50;</code></p>
<p><code>m[2]=3.67;</code></p>
<p><code>m[3]=10.5;</code></p>
<p><code>m[4]=45.8;</code></p>
<p><code>m[5]=0.0003;</code></p>
<p>Настолько просто, что легко упустить, что же здесь, собственно, происходит. А это очень плохо, потому что в действительности происходящее может заметно ухудшить быстродействие программы.</p>
<p>Функция operator[] контейнеров map никак не связана с функциями operator[] контейнеров vector, deque и string, а также со встроенным оператором [ ], работающим с массивами. Функция <code>map::operator[]</code> упрощает операции «обновления с возможным созданием». Иначе говоря, при наличии объявления map&lt;K, V&gt; m команда m[k]=v; проверяет, присутствует и ключ к в контейнере. Если ключ отсутствует, он добавляется вместе с ассоциированным значением v. Если ключ уже присутствует, ассоциированное с ним значение заменяется на v.</p>
<p>Для этого operator [] возвращает ссылку на объект значения, ассоциированного с ключом к, после чего v присваивается объекту, к которому относится эта ссылка. При обновлении значения, ассоциированного с существующим ключом, никаких затруднений не возникает — в контейнере уже имеется объект, ссылка на который возвращается функцией operator[]. Но при отсутствии ключа к готового объекта, на который можно было бы вернуть ссылку, не существует. В этом случае объект создается конструктором по умолчанию, после чего operator [] возвращает ссылку на созданный объект.</p>
<p>Вернемся к началу исходного примера:</p>
<p><code>map&lt;int,Widget&gt; m;</code></p>
<p><code>m[1]=1.50;</code></p>
<p>Выражение m[1] представляет собой сокращенную запись для <code>m.operator[](1)</code>, поэтому во второй строке присутствует вызов map:: operator[]. Функция должна вернуть ссылку на ассоциированный объект Widget. В данном примере m еще не содержит ни одного элемента, поэтому элемент с ключом 1 не существует. Конструктор по умолчанию создает объект Widget, ассоциируемый с ключом 1, и возвращает ссылку на этот объект. Наконец, созданному объекту Widget присваивается значение 1.50.</p>
<p>Иначе говоря, команда</p>
<p>m[1]=1.50:</p>
<p>функционально эквивалентна следующему фрагменту:</p>
<p><code>typedef map&lt;int,Widget&gt; intWidgetMap: // Вспомогательное определение типа</code></p>
<p><code>pair&lt;intWidgetMap::iterator.bool&gt; result =//'Создание нового</code></p>
<p><code>m.insert(intWidgetMap::value_type(1,Widget())); // элемента с ключом 1</code></p>
<p><code>// и ассоциированным объектом, созданным </code></p>
<p><code>// конструктором по умолчанию; комментарии </code></p>
<p><code>// по поводу value_type // приведены далее</code></p>
<p><code>result.first-&gt;second = 1.50;// Присваивание значения</code></p>
<p><code>// созданному объекту</code></p>
<p>Теперь понятно, почему такой подход ухудшает быстродействие программы. Сначала мы конструируем объект Widget, а затем немедленно присваиваем ему новое значение. Конечно, правильнее было бы сразу сконструировать Widget с нужными данными вместо того, чтобы конструировать Widget по умолчанию и затем выполнять присваивание. Следовательно, вызов operator[] было бы правильнее заменить прямолинейным вызовом insert:</p>
<p><code>m.insert(intWidgetMap::value_type(1,1.50));</code></p>
<p>С функциональной точки зрения эта конструкция эквивалентна фрагменту, приведенному выше, но она позволяет сэкономить три вызова функций: создание временного объекта Widget конструктором по умолчанию, уничтожение этого временного объекта и оператор присваивания Widget. Чем дороже обходятся эти вызовы, тем большую экономию обеспечивает применение map:: insert вместо map::operator[].</p>
<p>В приведенном выше фрагменте используется определение типа value_type, предоставляемое всеми стандартными контейнерами. Помните, что для map и multimap (а также для нестандартных контейнеров hash_map и hash_multimap — совет 25) тип элемента всегда представляет собой некую разновидность pair.</p>
<p>Я уже упоминал о том, что operator[] упрощает операции «обновления с возможным созданием». Теперь мы знаем, что при создании insert работает эффективнее, чем operator[]<strong>. </strong>При обновлении, то есть при наличии эквивалентного ключа (см. совет 19) в контейнере map, ситуация полностью меняется. Чтобы понять, почему это происходит, рассмотрим потенциальные варианты обновления:</p>
<p><code>m[k] = v; // Значение, ассоциируемое</code></p>
<p><code>// с ключом к.заменяется на v при помощи оператора []</code></p>
<p><code>m.insert(intWidgetMap::value_type(k,v)).first-&gt;second = v; // Значение, ассоциируемое</code></p>
<p><code>// с ключом к, заменяется на v при помощи insert</code></p>
<p>Вероятно, один внешний вид этих команд заставит вас выбрать operator[]<strong>, </strong>но в данном случае речь идет об эффективности, поэтому фактор наглядности не учитывается.</p>
<p>При вызове insert передается аргумент типа inWidgetMap::value_type (то есть pair&lt;int,Widget&gt;), потому при вызове insert необходимо сконструировать и уничтожить объект данного типа. Следовательно, при вызове insert будут вызваны конструктор и деструктор pair, что в свою очередь приведет к вызову конструктора и деструктора Widget, поскольку pair&lt;in,Widget&gt; содержит объект Widget. При вызове operator[] объект pair не используется, что позволяет избежать затрат на конструирование и уничтожение pair и Widget.</p>
<p>Следовательно, при вставке элемента в map по соображениям эффективности желательно использовать insert вместо operator[], а при обновлении существующих элементов предпочтение отдается operator[], что объясняется как эффективностью, так и эстетическими соображениями.</p>
<p>Конечно, нам хотелось бы видеть в STL функцию, которая бы автоматически выбирала оптимальное решение в синтаксически привлекательном виде. Интерфейс вызова мог бы выглядеть следующим образом:</p>
<p><code>iterator affectedPair =// Если ключ к отсутствует в контейнере m.</code></p>
<p><code>efficentAddOrUpdate(m,k,v); // выполнить эффективное добавление</code></p>
<p><code>// pair(k.v) в m: в противном случае </code></p>
<p><code>// выполнить эффективное обновление </code></p>
<p><code>// значения, ассоциированного с ключом к.</code></p>
<p><code>// Функция возвращает итератор </code></p>
<p><code>// для добавленной или измененной пары</code></p>
<p>В STL такая функция отсутствует, но как видно из следующего фрагмента, ее нетрудно написать самостоятельно. В комментариях даются краткие пояснения, а дополнительная информация приведена после листинга.</p>
<p><code>template&lt;typename МарТуре,// Тип контейнера</code></p>
<p><code>	typename KeyArgType, // Причины для передачи параметров-типов 	typename ValueArgType&gt; // KeyArgType и ValueArgType </code></p>
<p><code>		// приведены ниже</code></p>
<p><code>typename МарТуре::iterator</code></p>
<p><code>efficientAddOrUpdate(MapType&amp; m.</code></p>
<p><code>	const KeyArgType&amp; k.</code></p>
<p><code>	const ValueArgType&amp; v)</code></p>
<p><code>{</code></p>
<p><code>typename МарТуре:iterator lb = // Определить, где находится</code></p>
<p><code>// или должен находиться ключ к.</code></p>
<p><code>m.lower_bound(k);// Ключевое слово typename</code></p>
<p><code>// рассматривается на с. 20</code></p>
<p><code>if (lb!=m.end())&amp;&amp; !(m.key_comp()(k.lb-&gt;first))){ // Если lb ссылается на пару.</code></p>
<p><code>// ключ которой эквивалентен к</code></p>
<p><code>	lb-&gt;second = v;// ...обновить ассоциируемое значение</code></p>
<p><code>	return lb;	//и вернуть итератор для найденной пары</code></p>
<p><code>}</code></p>
<p><code>else{</code></p>
<p><code>	typedef typename МарТуре::value_type MVT;</code></p>
<p><code>	return m.insert(lb.MVT(k.v)); // Включить pair(k.v) в m и вернуть</code></p>
<p><code>	// итератор для нового элемента</code></p>
<p><code>}</code></p>
<p><code>}</code></p>
<p>Для эффективного выполнения операций создания и обновления необходимо узнать, присутствует ли ключ к в контейнере; если присутствует — где он находится, а если нет — где он должен находиться. Задача идеально подходит для функции lower_bound (совет 45). Чтобы определить, обнаружила ли функция lower_bound элемент с нужным ключом, мы проверяем вторую половину условия эквивалентности (см. совет 19). При этом сравнение должно производиться функцией, полученной при вызове map::кеуcomp. В результате проверки эквивалентности мы узнаем, какая операция выполняется — создание или обновление.</p>
<p>Обновление реализовано весьма прямолинейно. С созданием дело обстоит поинтереснее, поскольку в нем используется «рекомендательная» форма insert. Конструкция m.insert(lb.MVT(k, v)) «рекомендует» lb как правильную точку вставки для нового элемента с ключом, эквивалентным к, а Стандарт гарантирует, что в случае правильности рекомендации вставка будет выполнена за постоянное время (вместо логарифмического). В efficentAddOrUpdate мы знаем, что lb определяет правильную позицию вставки, поэтому insert всегда выполняется с постоянным временем.</p>
<p>У данной реализации есть одна интересная особенность — KeyArgType и ValueArgType не обязаны быть типами, хранящимися в контейнере, а всего лишь должны <emphasis><strong>приводиться </strong></emphasis>к этим типам. Существует и другое возможное решение — удалить параметры-типы KeyArgType/ValueArgType и заменить их на <code>МарТуре::key_type</code> и <code>МарТуре::mapped_type</code>. Но в этом случае вызов может сопровождаться лишними преобразованиями типов. Возьмем определение контейнера map, встречавшееся в примерах:</p>
<p><code>map&lt;int,Widget&gt; m;// См. ранее</code></p>
<p>Также вспомним, что Widget допускает присваивание значений типа double:</p>
<p><code>class Widget {//См. ранее</code></p>
<p><code>public:</code></p>
<p><code>Widget&amp; operator=(double weight);</code></p>
<p><code>};</code></p>
<p>Теперь рассмотрим следующий вызов efficientAddOrllpdate:</p>
<p><code>effcientAddOrUpdate(m,10,15);</code></p>
<p>Допустим, выполняется операция обновления, то есть m уже содержит элемент с ключом 10. В этом случае приведенный ранее шаблон заключает, что ValueArgType является double, и в теле функции число 1.5 в формате double нацрямую присваивается объекту Widget, ассоциированному с ключом 10. Присваивание осуществляется вызовом <code>Widget::operator=(double)</code>. Если бы третий параметр efficientAddOrUpdate относился к типу <code>МарТуре:: mapped_type</code>, то число 1.5 в момент вызова было бы преобразовано в Widget, что привело бы к лишним затратам на конструирование (и последующее уничтожение) объекта Widget.</p>
<p>Сколь бы интересными не были тонкости реализации efficientAddOrUpdate, не будем отклоняться от главной темы этого совета — от необходимости тщательного выбора между <code>map::operator[]</code> и <code>map::insert</code> в тех случаях, когда важна эффективность выполняемых операций. При обновлении существующего элемента map рекомендуется использовать оператор [ ], но при создании нового элемента предпочтение отдается insert.</p>
</section>
<section>
<title>
<p>Совет 25. Изучите нестандартные хэшированные контейнеры</p>
</title>
<p>После первого знакомства с STL у большинства программистов неизбежно возникает вопрос: «Векторы, списки, множества... хорошо, но где же хэш-таблицы?» Действительно, хэш-таблицы не входят в стандартную библиотеку С++. Все сходятся на том, что это досадное упущение, но Комитет по стандартизации С++ решил, что усилия, затраченные на их поддержку, привели бы к чрезмерной задержке в работе над стандартом. По всей вероятности, хэш-таблицы появятся в следующей версии Стандарта, но в настоящий момент хеширование не поддерживается в STL.</p>
<p>Программисты, не печальтесь! Вам не придется обходиться без хэш-таблиц или создавать собственные реализации. Существует немало готовых STL-совместимых хэшированных ассоциативных контейнеров с вполне стандартными именами: hash_set, hash_multiset, hash_map  и hash_multimap.</p>
<p>Реализации, скрытые за похожими именами... мягко говоря, не похожи друг на друга. Различается все: интерфейсы, возможности, структуры данных и относительная эффективность поддерживаемых операций, Можно написать более или менее переносимый код, использующий хэш-таблицы, но стандартизация хэшированных контейнеров значительно упростила бы эту задачу (теперь понятно, почему стандарты так важны),</p>
<p>Из всех существующих реализаций хэшированных контейнеров наибольшее распространение получили две: от SGI (совет 50) и от Dinkumware (приложение Б), поэтому дальнейшее описание ограничивается устройством хешированных контейнеров от этих разработчиков. STLport (совет 50) также содержит хэшированные контейнеры, но они базируются на реализации SGI. В контексте настоящего примера все сказанное о хэшированных контейнерах SGI относится и к хэшированным контейнерам STLport.</p>
<p>Хэшированные контейнеры относятся к категории ассоциативных, поэтому им, как и всем остальным ассоциативным контейнерам, при объявлении следует задать тип объектов, хранящихся в контейнере, функцию сравнения для этих объектов и распределитель памяти. Кроме того, для работы хэшированному контейнеру необходима хэш-функция. Естественно предположить, что объявление хэшированного контейнера должно выглядеть примерно так:</p>
<p><code>template&lt;typename Т,</code></p>
<p><code>	typename HashFunction,</code></p>
<p><code>	typename CompareFunction,</code></p>
<p><code>	typename Allocator = allocator&lt;T&gt; &gt;</code></p>
<p><code>class hash<emphasis>_контейнер</emphasis>;</code></p>
<p>Полученное объявление весьма близко к объявлению хэшированных контейнеров в реализации SGI. Главное различие между ними заключается в том, что в реализации SGI для типов HashFunction и CompareFunction предусмотрены значения по умолчанию. Объявление hash_set в реализации SGI выглядит следующим образом (слегка исправлено для удобства чтения):</p>
<p><code>template&lt;typename Т,</code></p>
<p><code>	typename HashFunction = hash&lt;T&gt;,</code></p>
<p><code>	typename CompareFunction = equal_to&lt;T&gt;,</code></p>
<p><code>	typename Allocator = allocator&lt;T&gt; &gt;</code></p>
<p><code>class hash_set;</code></p>
<p>В реализации SGI следует обратить внимание на использование equal_to в качестве функции сравнения по умолчанию. В этом она отличается от стандартных ассоциативных контейнеров, где по умолчанию используется функция сравнения less. Смысл этого изменения не сводится к простой замене функции. Хэшированные контейнеры SGI сравнивают два объекта, проверяя <emphasis><strong>их равенство, </strong></emphasis>а <emphasis><strong>неэквивалентность </strong></emphasis>(см. совет 19), Для хэшированных контейнеров такое решение вполне разумно, поскольку в хэшированных ассоциативных контейнерах, в отличие от их стандартных аналогов (обычно построенных на базе деревьев), элементы не хранятся в отсортированном порядке.</p>
<p>В реализации Dinkumware принят несколько иной подход. Она также позволяет задать тип объектов, хэш-функцию, функцию сравнения и распределитель, но хэш-функция и функция сравнения по умолчанию перемещены в отдельный класс hash_compare, который передается по умолчанию в параметре HashingInfo шаблона контейнера.</p>
<p>Например, объявление hash_set (также отформатированное для наглядности) в реализации Dinkumware выглядит следующим образом:</p>
<p><code>template&lt;typename Т,typename CompareFunction&gt;</code></p>
<p><code>class hash_compare;</code></p>
<p><code>template&lt;typename T,</code></p>
<p><code>	typename Hashinglnfo = hash_compare&lt;T,less&lt;T&gt;&gt;,</code></p>
<p><code>	typename Allocator = allocator&lt;T&gt;&gt;</code></p>
<p><code>class hash_set;</code></p>
<p>В этом интерфейсе внимание стоит обратить на использование параметра HashingInfo, содержащего функции хэширования и сравнения, а также перечисляемые типы, управляющие минимальным количеством гнезд в таблице и максимальным допустимым отношением числа элементов контейнера к числу гнезд. В случае превышения пороговой величины количество гнезд в таблице увеличивается, а некоторые элементы в таблице хэшируются заново (в реализации SGI предусмотрены функции, обеспечивающие аналогичные возможности управления количеством гнезд в таблице).</p>
<p>После небольшого форматирования объявление hash_compare (значение по умолчанию для HashingInfo) выглядит примерно так:</p>
<p><code>template&lt;typename Т,typename CompareFunction=less&lt;T&gt;&gt;</code></p>
<p><code>class hash_compare{</code></p>
<p><code>	public:</code></p>
<p><code>	enum{</code></p>
<p><code>	bucket_size = 4. // Максимальное отношение числа элементов к числу гнезд </code></p>
<p><code>	min_buckets = 8 // Минимальное количество гнезд</code></p>
<p><code>}</code></p>
<p><code>size_t operator()(const Т&amp;) const; // Хэш-функция</code></p>
<p><code>bool operator() (const T&amp;,</code></p>
<p><code>	const T&amp;) const;</code></p>
<p><code>// Некоторые подробности опущены,</code></p>
<p><code>// включая использование CompareFunction</code></p>
<p><code>};</code></p>
<p>Перегрузка operator() (в данном случае для реализации функций хэширования и сравнения) используется гораздо чаще, чем можно представить. Другое применение этой концепции продемонстрировано в совете 23.</p>
<p>Реализация Dinkumware позволяет программисту написать собственный касс-аналог hash_compare (возможно, объявленный производным от hash_compare). Если этот класс будет определять bucket_size, min_buckets, две функции operator() (с одним и с двумя аргументами) и еще несколько мелочей, не упомянутых выше, он может использоваться для управления конфигурацией и поведением контейнеров Dinkumware hash_set и hash_multiset. Управление конфигурацией hash_mnap и hash_ multimap осуществляется аналогичным образом.</p>
<p>Учтите, что в обоих вариантах все принятие решений можно поручить реализации и ограничиться объявлением следующего вида:</p>
<p><code>hash_set&lt;int&gt; intTable; // Создать хешированное множество int</code></p>
<p>Чтобы это объявление нормально компилировалось, хэш-таблица должна содержать данные целочисленных типов (например, int), поскольку стандартные хэш-функции обычно ограничиваются целочисленными типами (в реализации SGI стандартные хэш-функции обладают более широкими возможностями; о том, где найти дополнительную информацию, рассказано в совете 50).</p>
<p>Принципы внутреннего устройства реализаций SGI и Dinkumware очень сильно различаются. В реализации SGI использована традиционная схема открытого хэширования с массивом указателей на односвязные списки элементов. В реализации Dinkumware используется двусвязный список. Различие достаточно принципиальное, поскольку оно влияет на категории итераторов, поддерживаемых этими реализациями. Хэшированные контейнеры SGI поддерживают прямые итераторы, что исключает возможность обратного перебора; в них отсутствуют такие функции, как rbegin или rend. Реализация Dinkumware поддерживает двусторонние итераторы, что позволяет осуществлять перебор как в прямом, так и в обратном направлении. С другой стороны, реализация SGI чуть экономнее расходует память.</p>
<p>Какая из этих реализаций лучше подходит для ваших целей? Понятия не имею. Только вы можете ответить на этот вопрос, однако в этом совете я даже не пытался изложить все необходимое для принятия обоснованного решения. Речь идет о другом — вы должны знать, что несмотря на отсутствие хэшированных контейнеров непосредственно в STL, при необходимости можно легко найти STL-совместимые хэшированные контейнеры (с разными интерфейсами, возможностями и особенностями работы). Более того, в свободно распространяемых реализациях SGI и STLport вам за них даже не придется платить.</p>
</section>
</section>
<section>
<title>
<p>Итераторы</p>
</title>
<section>
<p>На первый взгляд итераторы представляются предметом весьма простым. Но стоит присмотреться повнимательнее, и вы заметите, что стандартные контейнеры STL поддерживают четыре разных типа итераторов: <code>iterator, const_iterator, reverse_iterator</code> и <code>const_reverse_iterator</code>. Проходит совсем немного времени, и выясняется, что в некоторых формах insert и erase только один из этих четырех типов принимается контейнером. И здесь начинаются вопросы. Зачем нужны четыре типа итераторов? Существует ли между ними какая-либо связь? Можно ли преобразовать итератор от одного типа к другому? Можно ли смешивать разные типы итераторов при вызове алгоритмов и вспомогательных функций STL? Как эти типы связаны с контейнерами и их функциями?</p>
<p>В настоящей главе вы найдете ответы на эти вопросы, а также поближе познакомитесь с разновидностью итераторов, которой обычно не уделяют должного внимания: isreambuf_iterator. Если вам нравится STL, но не устраивает быстродействие istream_iterator при чтении символьных потоков, возможно, isreambuf_ iterator поможет справиться с затруднениями.</p>
</section>
<section>
<title>
<p>Совет 26. Старайтесь использовать iterator вместо const_iterator, reverse_iterator и const_reverse_iterator</p>
</title>
<p>Как известно, каждый стандартный контейнер поддерживает четыре типа итераторов. Для контейнера container&lt;T&gt; тип iterator работает как Т* тогда как const_iterator работает как const Т* (также встречается запись Т const*). При увеличении iterator или const_iterator происходит переход к следующему элементу контейнера в прямом порядке перебора (от начала к концу контейнера). Итераторы reverse_iterator и const_reverse_iterator также работают как Т* и const Т* соответственно, но при увеличении эти итераторы переходят к следующему элементу в обратном порядке перебора (от конца к началу).</p>
<p>Рассмотрим несколько сигнатур insert и erase в контейнере vector&lt;T&gt;:</p>
<p><code>iterator insert(iterator position, const T&amp; x);</code></p>
<p><code>iterator erase (iterator position);</code></p>
<p><code>iterator erase ( iterator rangeBegin,  iterator rangeEnd);</code></p>
<empty-line/>
<p>Аналогичные функции имеются у всех стандартных контейнеров, но тип возвращаемого значения определяется типом контейнера. Обратите внимание: перечисленные функции требуют передачу параметров типа <code>iterator. </code>Не  <code>const_iterator </code>, не <code>reverse_iterator</code> и не <code>const_reverse_iterator</code> — только <code>iterator</code>. Хотя контейнеры поддерживают четыре типа итераторов, один из этих типов обладает привилегиями, отсутствующими у других типов. Тип <code>iterator</code> занимает особое место.</p>
<empty-line/>
<p>На следующей диаграмме показаны преобразования, возможные между итераторами разных типов.</p>
<image l:href="#img_6.png"/>
<empty-line/>
<empty-line/>
<p>Из рисунка следует, что <code>iterator</code> преобразуется в <code>const_iterator</code> и <code>reverse_ iterator</code>, а <code>reverse_iterator</code> — в <code>const_reverse_iterator</code>. Кроме того, reverse_iterator преобразуется в iterator при помощи функции base типа reverse_iterator, a const_ reverse_iterator аналогичным образом преобразуется в const_iterator. Однако из рисунка не видно, что итераторы, полученные при вызове base, могут оказаться не теми, которые вам нужны. За подробностями обращайтесь к совету 28.</p>
<p>Обратите внимание: не существует пути от <code>const_iterator</code> к <code>iterator</code> или от <code>const_reverse_iterator</code> к <code>reverse_iterator</code>. Из этого важного обстоятельства следует, что <code>const_iterator</code> и const_reverse_iterator могут вызвать затруднения с некоторыми функциями контейнеров. Таким функциям необходим тип iterator, а из-за отсутствия обратного перехода от const-итераторов к iterator первые становятся в целом бесполезными, если вы хотите использовать их для определения позиции вставки или удаления элементов.</p>
<p>Однако не стоит поспешно заключать, что const-итераторы вообще бесполезны. Это не так. Они прекрасно работают с алгоритмами, поскольку для алгоритмов обычно подходят все типы итераторов, относящиеся к нужной категории. Кроме того, const-итераторы подходят для многих функций контейнеров. Проблемы возникают лишь с некоторыми формами insert и erase.</p>
<p>Обратите внимание на формулировку: const-итераторы становятся <emphasis><strong>в целом </strong></emphasis>бесполезными, если вы хотите использовать их для определения позиции вставки или удаления элементов. Называть их полностью бесполезными было бы неправильно. Const-итераторы могут принести пользу, если вы найдете способ получения iterator для const_iterator или const_reverse_iterator. Такое возможно часто, но далеко не всегда, причем даже в благоприятном случае решение не очевидно, да и эффективным его не назовешь. В двух словах этот вопрос не изложить, если вас заинтересуют подробности — обращайтесь к совету 27. А пока имеющаяся информация позволяет понять, почему типу iterator отдается предпочтение перед его const- и reverse-аналогами.</p>
<p>•Некоторым версиям insert и erase при вызове должен передаваться тип iterator. Const- и reverse-итераторы им не подходят.</p>
<p>•Автоматическое преобразование const-итератора в iterator невозможно, а методика получения iterator на основании const_iterator (совет 27) применима не всегда, да и эффективность ее не гарантируется.</p>
<p>•Преобразование reverse_iterator в iterator может требовать дополнительной регулировки итератора. В совете 28 рассказано, когда и почему возникает такая необходимость.</p>
<p>Из сказанного следует однозначный вывод: если вы хотите работать с контейнерами просто и эффективно и по возможности застраховаться от нетривиальных ошибок, выбирайте iterator вместо его cons<emphasis><strong>t- </strong></emphasis>и reverse-аналогов.</p>
<p>На практике выбирать обычно приходится между iterator и const_iterator<emphasis><strong>. </strong></emphasis>Выбор между iterator и reverse_iterator часто происходит помимо вашей воли — все зависит от того, в каком порядке должны перебираться элементы контейнера (в прямом или в обратном). А если после выбора reverse_iterator потребуется вызвать функцию контейнера, требующую iterator, вызовите функцию base (возможно, с предварительной регулировкой смещения — см. совет 28).</p>
<p>При выборе между iterator и const_iterator рекомендуется выбирать iterator даже в том случае, если можно обойтись const_iterator, а использование iterator не обусловлено необходимостью вызова функции контейнера. В частности, немало хлопот возникает при сравнениях iterator с const_iterator. Думаю, вы согласитесь, что следующий фрагмент выглядит вполне логично:</p>
<p><code>typedef deque&lt;int&gt; IntDeque;// Определения типов</code></p>
<p><code>typedef IntDeque:iterator Iter;// упрощают работу</code></p>
<p><code>typedef IntDeque::const_iterator  Constlter; // с контейнерами STL</code></p>
<p><code>// и типами итераторов</code></p>
<p><code>iter i;</code></p>
<p><code>Constlter ci:</code></p>
<p><code>// i и ci указывают на элементы // одного контейнера</code></p>
<p><code>if (i=ci)...// Сравнить iterator</code></p>
<p><code><emphasis>//c </emphasis>const_iterator</code></p>
<p>В данном примере происходит обычное сравнение двух итераторов контейнера, подобные сравнения совершаются в STL сплошь и рядом. Просто один объект относится к типу iterator, а другой — к типу const_iterator. Проблем быть не должно — iterator автоматически преобразуется в const_iterator<emphasis><strong>, </strong></emphasis>и в сравнении участвуют два const_iterator.</p>
<p>Именно это и происходит в хорошо спроектированных реализациях STL, но в некоторых случаях приведенный фрагмент не компилируется. Причина заключается в том, что такие реализации объявляют operator<emphasis><strong>= </strong></emphasis>функцией класса const_iterator вместо внешней функции. Впрочем, вас, вероятно, больше интересуют не корни проблемы, а ее решение, которое заключается в простом изменении порядка итераторов:</p>
<p><code>if (c=i)...// Обходное решение для тех случаев,</code></p>
<p><code>// когда приведенное выше сравнение не работает</code></p>
<p>Подобные проблемы возникают не только при сравнении, но и вообще при смешанном использовании iterator и const_iterator (или reverse_iterator и const_ reverse_iterator) в одном выражении. например, при попытке вычесть один итератор произвольного доступа из другого:</p>
<p><code>if (i-ci&gt;=3)... // Если i находится минимум в трех позициях после ci...</code></p>
<p>ваш (правильный) код будет несправедливо отвергнут компилятором, если итераторы относятся к разным типам. Обходное решение остается прежним (перестановка <code>i</code> и <code>ci</code>), но в этом случае приходится учитывать, что <code>i-ci</code> не заменяется на <code>ci-i</code>:</p>
<p><code>if (c+3&lt;=i)... // Обходное решение на случай, если</code></p>
<p><code>// предыдущая команда не компилируется</code></p>
<p>Простейшая страховка от подобных проблем заключается в том, чтобы свести к минимуму использование разнотипных итераторов, а это в свою очередь подсказывает, что вместо const_iterator следует использовать iterator. На первый взгляд отказ от const_iterator только для предотвращения потенциальных недостатков реализации (к тому же имеющих обходное решение) выглядит неоправданным, но с учетом особого статуса iterator в некоторых функциях контейнеров мы неизбежно приходим к выводу, что итераторы const_iterator менее практичны, а хлопоты с ними иногда просто не оправдывают затраченных усилий.</p>
</section>
<section>
<title>
<p>Совет 27. Используйте distance и advance для преобразования const_iterator в iterator</p>
</title>
<p>Как было сказано в совете 26, некоторые функции контейнеров, вызываемые с параметрами-итераторами, ограничиваются типом iterator; const_iterator им не подходит. Что же делать, если имеется const_iterator и вы хотите вставить новый элемент в позицию контейнера, обозначенную этим итератором? Const_iterator необходимо каким-то образом преобразовать в iterator, и вы должны принять в этом активное участие, поскольку, как было показано в совете 26, автоматического преобразования const_iterator в iterator не существует.</p>
<p>Я знаю, о чем вы думаете. «Если ничего не помогает, берем кувалду», не так ля? В мире С++ это может означать лишь одно: преобразование типа. Стыдитесь. И где вы набрались таких мыслей?</p>
<p>Давайте разберемся с вредным заблуждением относительно преобразования типа. Посмотрим, что происходит при преобразовании const_iterator в iterator:</p>
<p><code>typedef deque&lt;int&gt; IntDeque:// Вспомогательные определения типов</code></p>
<p><code>typedef IntDeque::iterator Iter;</code></p>
<p><code>typedef IntDeque::const_iterator Constlter;</code></p>
<p><code>Constlter ci</code></p>
<p><code>// ci - const iterator</code></p>
<p><code>Iter i(ci);// Ошибка! He существует автоматического</code></p>
<p><code>// преобразования const_iterator // в iterator</code></p>
<p><code>Iter i(const_cast&lt;Iter&gt;(ci)): // Ошибка! Преобразование const_iterator</code></p>
<p><code>// в iterator невозможно!</code></p>
<p>В приведенном примере используется контейнер deque, но аналогичный результат будет получен и для <code>list, set, muliset, mulimap</code> и хэшированных контейнеров, упоминавшихся в совете 25. Возможно, строка с преобразованием будет откомпилирована для vector и string, но это особые случаи, которые будут рассмотрены ниже.</p>
<p>Почему же для этих типов контейнеров преобразование не компилируется? Потому что <code>iterator</code> и <code>const_iterator</code> относятся к разным классам, и сходства между ними не больше, чем между <code>string</code> и <code>complex&lt;double&gt;</code>. Попытка преобразования одного типа в другой абсолютно бессмысленна, поэтому вызов <code>const_cast</code> будет отвергнут. Попытки использования <code>static_cast</code>, <code>reintepreter_cast</code> и преобразования в стиле С приведут к тому же результату.</p>
<p>Впрочем, некомпилируемое преобразование все же может откомпилироваться, если итераторы относятся к контейнеру <code>vector</code> или string. Это объясняется тем, что в реализациях данных контейнеров в качестве итераторов обычно используются указатели. В этих реализациях <code>vector&lt;T&gt;::iterator</code> является определением типа для <code>Т*, vector&lt;T&gt;:: const_iterator</code> — для <code>const Т*</code>, <code>string::iterator</code> — для <code>char*,</code> а <code>string:: const_iterator</code> — для <code>const char*</code>. В реализациях данных контейнеров преобразование <code>const_iterator</code> в <code>iterator</code> вызовом <code>const_cast</code> компилируется и даже правильно работает, поскольку оно преобразует <code>const Т*</code> в <code>Т*</code>. Впрочем, даже в этих реализациях <code>reverse_iterator</code> и <code>const_reverse_iterator</code> являются полноценными классами, поэтому const_cast не позволяет преобразовать <code>const_reverse_iterator</code> в <code>reverse_iterator</code>. Кроме того, как объясняется в совете 50, даже реализации, в которых итераторы контейнеров vector и string представлены указателями, могут использовать это представление лишь при компиляции окончательной (release) версии. Все перечисленные факторы приводят к мысли, что преобразование const-итераторов в итераторы не рекомендуется и для контейнеров vector и string, поскольку переносимость такого решения будет сомнительной.</p>
<p>Если у вас имеется доступ к контейнеру, от которого был взят const_iterator<emphasis><strong>, </strong></emphasis>существует безопасный, переносимый способ получения соответствующего типа iterator без нарушения системы типов. Ниже приведена основная часть этого решения (возможно, перед компиляцией потребуется внести небольшие изменения):</p>
<p><code>typedef deque&lt;int&gt; IntDeque;//См. ранее</code></p>
<p><code>typedef IntDeque::iterator Iter;</code></p>
<p><code>typedef IntDeque::const_iterator ConstIter;</code></p>
<p><code>IntDeque d;</code></p>
<p><code>Constlter ci;</code></p>
<p><code>// Присвоить ci ссылку на d </code></p>
<p><code>Iter i(d.begin());// Инициализировать i значением d.begin()</code></p>
<p><code>advance(i,distance(i,ci)); // Переместить i в позицию ci</code></p>
<p>Решение выглядит настолько простым и прямолинейным, что это невольно вызывает подозрения. Чтобы получить iterator, указывающий на тот же элемент контейнера, что и const_iterator, мы создаем новый iterator в начале контейнера и перемещаем его вперед до тех пор, пока он не удалится на то же расстояние, что и <code>const_iterator</code>! Задачу упрощают шаблоны функций <code>advance</code> и <code>distance</code>, объявленные в <code>&lt;iterator&gt;. Distance</code> возвращает расстояние между двумя итераторами в одном контейнере, a <code>advance</code> перемещает итератор на заданное расстояние. Когда итераторы i и ci относятся к одному контейнеру, выражение <code>advance( i, distance(i, ci))</code> переводит их в одну позицию контейнера.</p>
<p>Все хорошо, если бы этот вариант компилировался... но этого не происходит. Чтобы понять причины, рассмотрим объявление distance:</p>
<p><code>template&lt;typename InputIterator&gt;</code></p>
<p><code>typename iterator_traits&lt;InputIterator&gt;::difference_type</code></p>
<p><code>distance(InputIterator first, InputIterator last);</code></p>
<p> Не обращайте внимания на то, что тип возвращаемого значения состоит из 56 символов и содержит упоминания зависимых типов (таких как differenceype). Вместо этого проанализируем использование параметра-типа InputIterator:</p>
<p><code>template&lt;typename InputIterator&gt;</code></p>
<p><code>typename iterator_traits&lt;InputIterator&gt;::difference_type</code></p>
<p><code>distance(InputIterator first,InputIterator last);</code></p>
<p>При вызове distance компилятор должен определить тип, представленный InputIterator, для чего он анализирует аргументы, переданные при вызове. Еще раз посмотрим на вызов distance в приведенном выше коде:</p>
<p><code>advance(i,.distance(i,ci)); // Переместить i в позицию ci</code></p>
<p>При вызове передаются два параметра, i и ci. Параметр i относится к типу iter, который представляет собой определение типа для<code> deque&lt;int&gt;:: iterator</code>. Для компилятора это означает, что InputIterator при вызове distance( соответствует типу <code>deque&lt;int&gt;: iterator</code>. Однако ci относится к типу ConstIter, который представляет собой определение типа для <code>deque&lt;int&gt;::const_iterator</code>. Из <emphasis><strong>этого </strong></emphasis>следует, что InputIterator соответствует типу <code>deque&lt;int&gt;::const_iterator</code>. InputIterator никак не может соответствовать двум типам одновременно, поэтому вызов distance завершается неудачей и каким-нибудь запутанным сообщением об ошибке, из которого можно (или нельзя) понять, что компилятор не смог определить тип InputIterator.</p>
<p>Чтобы вызов нормально компилировался, необходимо ликвидировать неоднозначность. Для этого проще всего явно задать параметр-тип, используемый distance, и избавить компилятор от необходимости определять его самостоятельно:</p>
<p><code>advanced.distance&lt;ConstIter&gt;(i,ci)): // Вычислить расстояние между</code></p>
<p><code>// i и ci (как двумя const_iterator)</code></p>
<p><code>// и переместить i на это расстояние</code></p>
<p>Итак, теперь вы знаете, как при помощи advance и distance получить iterator, соответствующий заданному const_iterator, но до настоящего момента совершенно не рассматривался вопрос, представляющий большой практический интерес: насколько эффективна данная методика? Ответ прост: она эффективна настолько, насколько это позволяют итераторы. Для итераторов произвольного доступа, поддерживаемых контейнерами vector<emphasis><strong>, </strong></emphasis>string, deque и т. д., эта операция выполняется с постоянным временем. Для двусторонних итераторов (к этой категории относятся итераторы других стандартных контейнеров, а также некоторых реализаций хэшированных контейнеров — см. совет 25) эта операция выполняется с линейным временем.</p>
<p>Поскольку получение iterator, эквивалентного const_iterator, может потребовать линейного времени, и поскольку это вообще невозможно сделать при недоступности контейнера, к которому относится const_iterator, проанализируйте архитектурные решения, вследствие которых возникла необходимость получения iterator по const_iterator. Результат такого анализа станет дополнительным доводом в пользу совета 26, рекомендующего отдавать предпочтение iterator перед const- и reverse-итераторами.</p>
</section>
<section>
<title>
<p>Совет 28. Научитесь использовать функцию base</p>
</title>
<p>При вызове функции base для итератора reverse_iterator будет получен «соответствующий» iterator<emphasis><strong>, </strong></emphasis>однако из сказанного совершенно не ясно, что же при этом происходит. В качестве примера рассмотрим следующий фрагмент, который заносит в вектор числа 1-5, устанавливает reverse_iterator на элемент 3 и инициализирует iterator функцией base:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>v.reserve(5);</code></p>
<p><code>//См. совет 14</code></p>
<p><code>for (int i=1;i&lt;=5;<emphasis>++i){</emphasis></code></p>
<p><code><emphasis>	</emphasis>v.push_back(i);</code></p>
<p><code>	// Занести в вектор числа 1-5</code></p>
<p><code>	vector&lt;int&gt;::reverse_iterator ri=</code></p>
<p><code>	find(v.rbegin(),v.rend(),3);</code></p>
<p><code>vector&lt;int&gt;:: iterator<emphasis> i (</emphasis>ri.base());</code></p>
<p><code>// Установить ri на элемент 3</code></p>
<p><code>// Присвоить i результат вызова base </code></p>
<p><code>// для итератора ri</code></p>
<p>После выполнения этого фрагмента ситуация выглядит примерно так:</p>
<image l:href="#img_7.png"/>
<empty-line/>
<p>На рисунке видно характерное смещение reverse_iterator и соответствующего базового итератора, воспроизводящего смещение begin() и end() по отношению к begin() и end(), но найти на нем ответы на некоторые вопросы не удается. В частности, рисунок не объясняет, как использовать i для выполнения операций, которые должны были выполняться с ri<emphasis><strong>.</strong></emphasis></p>
<p>Как упоминалось в совете 26, некоторые функции контейнеров принимают в качестве параметров-итераторов только iterator. Поэтому если вы, допустим, захотите вставить новый элемент в позицию, определяемую итератором п, сделать это напрямую не удастся; функция insert контейнера vector не принимает reverse_ iterator. Аналогичная проблема возникает при удалении элемента, определяемого итератором г . Функции erase не соглашаются на reverse_iterator и принимают только iterator. Чтобы выполнить удаление или вставку, необходимо преобразовать reverse_iterator в iterator при помощи base, а затем воспользоваться iterator для выполнения нужной операции.</p>
<p>Допустим, потребовалось вставить в v новый элемент в позиции, определяемой итератором n. Для определенности будем считать, что вставляется число 99. Учитывая, что n на предыдущем рисунке используется для перебора справа налево, а новый элемент вставляется <emphasis><strong>перед </strong></emphasis>позицией итератора, определяющего позицию вставки, можно ожидать, что число 99 окажется перед числом 3 в обратном порядке перебора. Таким образом, после вставки вектор v будет выглядеть так:</p>
<image l:href="#img_8.png"/>
<empty-line/>
<p>Конечно, мы не можем использовать n для обозначения позиции вставки, поскольку это не iterator. Вместо этого необходимо использовать <emphasis><strong>i. </strong></emphasis>Как упоминалось выше, когда n указывает на элемент 3, i (то есть r. base()) указывает на элемент 4. Именно на эту позицию должен указывать итератор <emphasis><strong>i, </strong></emphasis>чтобы вставленный элемент оказался в той позиции, в которой он бы находился, если бы для вставки можно было использовать итератор r.</p>
<p>Заключение:</p>
<p>• чтобы эмулировать вставку в позицию, заданную итератором ri типа reverse_ iterator, выполните вставку в позицию r.base(). По отношению к операции вставки ri и r.base() эквивалентны, но r.base() в действительности представляет собой iterator, <emphasis><strong>соответствующий </strong></emphasis>ri.</p>
<p>Рассмотрим операцию удаления элемента. Вернемся к взаимосвязи между ri и исходным вектором (по состоянию на момент, предшествующий вставке значения 99):</p>
<image l:href="#img_9.png"/>
<empty-line/>
<p>Для удаления элемента, на который указывает итератор r, нельзя просто использовать , поскольку этот итератор ссылается на другой элемент. Вместо этого нужно удалить элемент, <emphasis><strong>предшествующий </strong><strong>i</strong><strong>. </strong></emphasis></p>
<p>Заключение:</p>
<p>• чтобы эмулировать удаление в позиции, заданной итератором ri типа reverse_ iterator, выполните удаление в позиции, предшествующей ri .base(). По отношению к операции удаления ri и ri .base() <emphasis><strong>не эквивалентны, </strong></emphasis>a ri .base() <emphasis><strong>не является </strong></emphasis>объектом iterator, соответствующим ri.</p>
<p>Однако к коду стоит присмотреться повнимательнее, поскольку вас ждет сюрприз:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>… // См. ранее. В вектор v заносятся // числа 1-5</code></p>
<p><code>vector&lt;int&gt;::reverse_iterator ri = // Установить ri на элемент 3</code></p>
<p><code>	find(v.rbegin(),v.rend(),3);</code></p>
<p><code>	v.erase(--ri .base());// Попытка стирания в позиции.</code></p>
<p><code>// предшествующей ri-base():</code></p>
<p><code>// для вектора обычно</code></p>
<p><code>// не компилируется</code></p>
<p>Решение выглядит вполне нормально. Выражение  <code>--ri.base()</code> правильно определяет элемент, предшествующий удаляемому. Более того, приведенный фрагмент будет нормально работать для всех стандартных контейнеров, за исключением vector и string. Наверное, он бы мог работать и для этих контейнеров, но во многих реализациях vector и string он не будет компилироваться. В таких реализациях типы iterator (и const_iterator) реализованы в виде встроенных указателей, поэтому результатом вызова <code>i.base()</code> является указатель. В соответствии с требованиями как С, так и С++ указатели, возвращаемые функциями, не могут модифицироваться, поэтому на таких платформах STL выражения типа <code>--i.base()</code> не компилируются. Чтобы удалить элемент в позиции, заданной итератором <code>reverse_iterator</code><emphasis><strong>, </strong></emphasis>и при этом сохранить переносимость, необходимо избегать модификации возвращаемого значения base. Впрочем, это несложно. Если мы не можем уменьшить результат вызова base, значит, нужно увеличить reverse_iterator и после этого вызвать base!</p>
<p>//См. ранее</p>
<p>v.erase((++ri).base()); // Удалить элемент, на который указывает ri;</p>
<p>// команда всегда компилируется</p>
<p>Такая методика работает во всех стандартных контейнерах и потому считается предпочтительным способом удаления элементов, определяемых итератором reverse_iterator.</p>
<p>Вероятно, вы уже поняли: говорить о том, что функция base класса reverse_ iterator возвращает «соответствующий» iterator, не совсем правильно. В отношении вставки это действительно так, а в отношении удаления — нет. При преобразовании reverse_iterator в iterator важно знать, какие операции будут выполняться с полученным объектом iterator. Только в этом случае вы сможете определить, подойдет ли он для ваших целей.</p>
</section>
<section>
<title>
<p>Совет 29. Рассмотрите возможность использования istreambuf_iterator при посимвольном вводе</p>
</title>
<p>Предположим, вы хотите скопировать текстовый файл в объект string. На первый взгляд следующее решение выглядит вполне разумно:</p>
<p><code>ifstream inputFile("interestringData.txt");</code></p>
<p><code>string fileData(istream_iterator&lt;char&gt;(inputFile)), // Прочитать inputFile 	istream iterator&lt;char&gt;0);// в fileData</code></p>
<p>Но вскоре выясняется, что приведенный синтаксис не копирует в строку пропуски (whitespace), входящие в файл. Это объясняется тем, что isream_iterator производит непосредственное чтение функциями operator&lt;&lt;, а эти функции по умолчанию не читают пропуски.</p>
<p>Чтобы сохранить пропуски, входящие в файл, достаточно включить режим чтения пропусков сбросом флага skipws для входного потока:</p>
<p><code>ifstream inputFile("interestingData.txt"):</code></p>
<p><code>inputFle.unset(ios::skipws);// Включить режим</code></p>
<p><code>// чтения пропусков </code></p>
<p><code>// в inputFile</code></p>
<p><code>string fileData(istream_iterator&lt;char&gt;(inputFile)), // Прочитать inputFile 	istream_iterator&lt;char&gt;0);// в fileData.</code></p>
<p>Теперь все символы InputFile копируются в fileData.</p>
<p>Кроме того, может выясниться, что копирование происходит не так быстро, как вам хотелось бы. Функции operator&lt;&lt;, от которых зависит работа stream_iterator, производят форматный ввод, а это означает, что каждый вызов сопровождается многочисленными служебными операциями. Они должны создать и уничтожить объекты sentry (специальные объекты потоков ввода-вывода, выполняющие начальные и завершающие операции при каждом вызове operator&lt;&lt;); они должны проверить состояние флагов, влияющих на их работу (таких, как skpws); они должны выполнить доскональную проверку ошибок чтения, а в случае обнаружения каких-либо проблем — проанализировать маску исключений потока и определить, нужно ли инициировать исключение. Все перечисленные операции действительно важны при форматном вводе, но если ваши потребности ограничиваются чтением следующего символа из входного потока, без них можно обойтись.</p>
<p>Более эффективное решение основано на использовании неприметного итератора istreambuf_iterator. Итераторы istreambuf_iterator работают аналогично istream_iterator, но если объекты istream_iterator&lt;char&gt; читают отдельные символы из входного потока оператором &lt;&lt;, то объекты streambuf_iterator обращаются прямо к буферу потока и непосредственно читают следующий символ (выражаясь точнее, объект streambuf_iterator&lt;char&gt; читает следующий символ из входного потока s вызовом s.rdbuf ()-&gt;sgetc()).</p>
<p>Перейти на использование istreambuf_iiterator при чтении файла так просто, что даже программист Visual Basic сделает это со второй попытки:</p>
<p>ifstream inputFile("interestingData.txt");</p>
<p>string fileData(istreambuf_iterator&lt;char&gt;(inputFile)). 	istreambuf_iterator&lt;char&gt;0);</p>
<p>На этот раз сбрасывать флаг skpws не нужно, итераторы streambuf_iterator никогда не пропускают символы при вводе и просто возвращают следующий символ из буфера.</p>
<p>По сравнению с istream_iterator это происходит относительно быстро. В проведенных мною простейших тестах выигрыш по скорости достигал 40%, хотя в вашем случае цифры могут быть другими. Не удивляйтесь, если быстродействие будет расти со временем; итераторы istreambuf_iterator населяют один из заброшенных уголков STL, и авторы реализаций еще недостаточно позаботились об их оптимизации. Например, в моих примитивных тестах итераторы istreambuf_iterator одной из реализаций работали всего на 5% быстрее, чем istream_iterator. В таких реализациях остается широкий простор для оптимизации i streambuf_iterator.</p>
<p>Если вы планируете читать из потока по одному символу, не нуждаетесь в средствах форматирования ввода и следите за эффективностью выполняемых операций, три лишних символа на итератор — не такая уж дорогая цена за заметный рост быстродействия. При неформатном посимвольном вводе всегда рассматривайте возможность применения sreambuf_iterator.</p>
<p>Раз уж речь зашла о буферизованных итераторах, следует упомянуть и об использовании osreambuf<emphasis><strong>_iterator</strong></emphasis> при неформатном посимвольном выводе. По сравнению с ostream_<emphasis><strong>iterator</strong></emphasis> итераторы ostream_bufiterator обладают меньшими затратами (при меньших возможностях), поэтому обычно они превосходят их по эффективности.</p>
</section>
</section>
<section>
<title>
<p>Алгоритмы</p>
</title>
<section>
<p>В начале главы 1 я упоминал о том, что львиная доля репутации STL связана с контейнерами, и это вполне объяснимо. Контейнеры обладают массой достоинств и упрощают повседневную работу бесчисленных программистов С++. Но и алгоритмы STL тоже по-своему замечательны и в той же степени облегчают бремя разработчика. Существует более 100 алгоритмов, и встречается мнение, что они предо-ставляют программисту более гибкий инструментарий по сравнению с контейнерами (которых всего-то восемь!). Возможно, недостаточное применение алгоритмов отчасти и объясняется их количеством. Разобраться в восьми типах контейнеров проще, чем запомнить имена и предназначение многочисленных алгоритмов.</p>
<p>В этой главе я постараюсь решить две основные задачи. Во-первых, я представлю некоторые малоизвестные алгоритмы и покажу, как с их помощью упростить себе жизнь. Не беспокойтесь, вам не придется запоминать длинные списки имен. Алгоритмы, представленные в этой главе, предназначены для решения повседневных задач — сравнение строк без учета регистра символов, эффективный поиск n объектов, в наибольшей степени соответствующих заданному критерию, обобщение характеристик всех объектов в заданном интервале и имитация copy_if (алгоритм из исходной реализации HP STL, исключенный в процессе стандартизации).</p>
<p>Во-вторых, я научу вас избегать стандартных ошибок, возникающих при работе с алгоритмами. Например, при вызове алгоритма remove и его родственников remove_if и unique необходимо точно знать, что эти алгоритмы делают (и чего они <emphasis><strong>не </strong></emphasis>делают). Данное правило особенно актуально при вызове remove для интервала, содержащего указатели. Многие алгоритмы работают только с отсортированными интервалами, и программист должен понимать, что это за алгоритмы и почему для них установлено подобное ограничение. Наконец, одна из наиболее распространенных ошибок, допускаемых при работе с алгоритмами, заключается в том, что программист предлагает алгоритму записать результаты своей работы в несуществующую область памяти. Я покажу, как это происходит и как предотвратить эту ошибку.</p>
<p>Возможно, к концу главы вы и не будете относиться к алгоритмам с тем же энтузиазмом, с которым обычно относятся к контейнерам, но по крайней мере будете чаще применять их в своей работе.</p>
</section>
<section>
<title>
<p>Совет 30. Следите за тем, чтобы приемный интервал имел достаточный размер</p>
</title>
<p>Контейнеры STL автоматически увеличиваются с добавлением новых объектов (функциями insert, push_front, push_back и т. д.). Автоматическое изменение размеров чрезвычайно удобно, и у многих программистов создается ложное впечатление, что контейнер сам обо всем позаботится и им никогда не придется следить за наличием свободного места. Если бы так!</p>
<p>Проблемы возникают в ситуации, когда программист <emphasis><strong>думает </strong></emphasis>о вставке объектов в контейнер, но не сообщает о своих мыслях STL. Типичный пример:</p>
<p><code>int transmogrify(int х); // Функция вычисляет некое новое значение</code></p>
<p><code>// по переданному параметру х</code></p>
<p><code>vector&lt;int&gt; values;</code></p>
<p><code>… // Заполнение вектора values данными</code></p>
<p><code>vector&lt;int&gt; results;// Применить transmogrify к каждому объекту</code></p>
<p><code>transform(values.begin(), // вектора values и присоединить возвращаемые</code></p>
<p><code>	values.end(). // значения к results.</code></p>
<p><code>	results.end. // Фрагмент содержит ошибку!</code></p>
<p><code>	transmogrify);</code></p>
<p>В приведенном примере алгоритм transform получает информацию о том, что приемный интервал начинается с results.end. С этой позиции он и начинает вывод значений, полученных в результате вызова transmogrify для каждого элемента values. Как и все алгоритмы, использующие приемный интервал, transform записывает свои результаты, присваивая значения элементам заданного интервала. Таким образом, transform вызовет transmogrify для values[0] и присвоит результат <code>*results.end()</code>. Затем функция transmogrify вызывается для values[l] с присваиванием результата <code>*(results.end()+1)</code>. Происходит катастрофа, поскольку в позиции <code>*results.end()</code> (и тем более в <code>*(results.end()+1) )</code> <emphasis><strong>не существует </strong><strong>объекта! </strong></emphasis>Вызов transform некорректен из-за попытки присвоить значение несуществующему объекту (в совете 50 объясняется, как отладочная реализация STL позволит обнаружить эту проблему на стадии выполнения).</p>
<p>Допуская подобную ошибку, программист почти всегда рассчитывает на то, что результаты вызова алгоритма будут вставлены в приемный контейнер вызовом insert. Если вы хотите, чтобы это произошло, так и скажите. В конце концов, STL — всего лишь библиотека, и читать мысли ей не положено. В нашем примере задача решается построением итератора, определяющего начало приемного интервала, вызовом back_inserter:</p>
<p><code>vector&lt;int&gt; values:</code></p>
<p><code>transform(values.begin(),// Применить transmogrify к каждому</code></p>
<p><code>	values.end(),// объекту вектора values</code></p>
<p><code>	back_inserter(results), // и дописать значения в конец results</code></p>
<p><code>	transmogrify);</code></p>
<p>При использовании итератора, возвращаемого при вызове back_inserter, вызывается push_back, поэтому back_inserter может использоваться со всеми контейнерами, поддерживающими push_back (то есть со всеми стандартными последовательными контейнерами: vector, string, deque и list<emphasis><strong>). </strong></emphasis>Если вы предпочитаете, чтобы алгоритм вставлял элементы в начало контейнера, Воспользуйтесь front_inserter. Во внутренней реализации front_inserter используется push_front, поэтому front_inserter работает только с контейнерами, поддерживающими эту функцию (то есть deque и list).</p>
<p><code>… //См. ранее</code></p>
<p><code>list<emphasis>&lt;i</emphasis>nt&gt; results;//Теперь используется</code></p>
<p><code>	//контейнер list</code></p>
<p><code>transform(values.begin(),values.end(). //Результаты вызова transform</code></p>
<p><code>front_inserter(results), //вставляются в начало results</code></p>
<p><code>transmogrify);//<emphasis>в обратном порядке</emphasis></code></p>
<p>Поскольку при использовании front_inserter новые элементы заносятся в начало results функцией push_front, порядок следования объектов в results будет <emphasis><strong>обратным </strong></emphasis>по отношению к порядку соответствующих объектов в values. Это ишь одна из причин, по которым front_inserter используется реже back_inserter. Другая причина заключается в том, что vector не поддерживает push_front, поэтому front_inserter не может использоваться с vector.</p>
<p>Чтобы результаты transform выводились в начале results, но с сохранением порядка следования элементов, достаточно перебрать содержимое values в обратном порядке:</p>
<p><code>list&lt;int&gt; results;// См. ранее</code></p>
<p><code>transform(values.rbegin().values.rend(). // Результаты вызова transform</code></p>
<p><code>	front_inserter(results), // вставляются в начало results </code></p>
<p><code>	transmogrify);</code></p>
<p><code>// с сохранением исходного порядка</code></p>
<p>Итак, front_<emphasis><strong>inserter</strong></emphasis> заставляет алгоритмы вставлять результаты своей работы в начало контейнера, a back_inserter обеспечивает вставку в конец контейнера. Вполне логично предположить, что inserter заставляет алгоритм выводить свои результаты с произвольной позиции:</p>
<p><code>vector&lt;int&gt; values;// См. ранее</code></p>
<p><code>vector&lt;int&gt; results;// См. ранее - за исключением того, что</code></p>
<p><code>	// results на этот раз содержит данные </code></p>
<p><code>	// перед вызовом transform.</code></p>
<p><code>transform(values.begin(),.// Результаты вызова transmogrify</code></p>
<p><code>	values.end(),// выводятся в середине results</code></p>
<p><code>	inserter (results, results. begin(_+results.size() /2),</code></p>
<p><code>	transmogrify);</code></p>
<p>Независимо от выбранной формы — <code>back_inserter, front_inserter</code> или <code>inserter</code> — объекты вставляются в приемный интервал по одному. Как объясняется в совете 5, это может привести к значительным затратам для блоковых контейнеров (vector, string и deque), однако средство, предложенное в совете 5 (интервальные функции), неприменимо в том случае, если вставка выполняется алгоритмом. В нашем примере transform записывает результаты в приемный интервал по одному элементу, и с этим ничего не поделаешь.</p>
<p>При вставке в контейнеры vector и string для сокращения затрат можно последовать совету 14 и заранее вызвать reserve. Затраты на сдвиг элементов при каждой вставке от этого не исчезнут, но по крайней мере вы избавитесь от необходимости перераспределения памяти контейнера:</p>
<p><code>vector&lt;int&gt; values;	// См. Ранее</code></p>
<p><code>...</code></p>
<p><code>vector&lt;int&gt; results;</code></p>
<p><code>...</code></p>
<p><code>results.<strong>reserve(</strong>results.size()+values.size()); // Обеспечить наличие</code></p>
<p><code>	// в векторе results</code></p>
<p><code>	// емкости для value.size()</code></p>
<p><code>	// элементов</code></p>
<p><code>transform(values.begin(), values.end(),	// То же, что и ранее,</code></p>
<p><code>	inserter(results,results.begin()+results.size()/2). // но без лишних transmogrify);</code></p>
<p><code>// перераспределений памяти</code></p>
<p>При использовании функции reserve для повышения эффективности серии вставок всегда помните, что reserve увеличивает только <emphasis><strong>емкость </strong></emphasis>контейнера, а размер остается неизменным. Даже после вызова reserve при работе с алгоритмом, который должен включать новые элементы в vector или string, необходимо использовать итератор вставки (то есть итератор, возвращаемый при вызове back_ inserter<emphasis><strong>, </strong></emphasis>front_inserter или inserter).</p>
<p>Чтобы это стало абсолютно ясно, рассмотрим <emphasis><strong>ошибочный </strong></emphasis>путь повышения эффективности для примера, приведенного в начале совета (с присоединением результатов обработки элементов values к results):</p>
<p><code>vector&lt;int&gt; values:// См. ранее</code></p>
<p><code>vector&lt;int&gt; results;</code></p>
<p><code>results.reserve(results.size()+values.size()); // См. Ранее</code></p>
<p><code>transform(values.begin(),values.end(),// Результаты вызова</code></p>
<p><code>	results.end(),// transmogrify записываются</code></p>
<p><code>	transmogrify);// в неинициализированную</code></p>
<p><code>// память; последствия </code></p>
<p><code>// непредсказуемы!</code></p>
<p>В этом фрагменте transform в блаженном неведении пытается выполнить присваивание в неинициализированной памяти за последним элементом results. Обычно подобные попытки приводят к ошибкам времени выполнения, поскольку операция присваивания имеет смысл лишь для двух объектов, но не между объектом и двоичным блоком с неизвестным содержимым. Но даже если этот код каким-то образом справится с задачей, вектор results не будет знать о новых «объектах», якобы созданных в его неиспользуемой памяти. С точки зрения results вектор после вызова transform сохраняет прежний размер, а его конечный итератор будет указывать на ту же позицию, на которую он указывал до вызова transform. Мораль? Использование reserve без итератора вставки приводит к непредсказуемым последствиям внутри алгоритмов и нарушению целостности данных в контейнере.</p>
<p>В правильном решении функция reserve используется в сочетании с итератором вставки:</p>
<p><code>vector&lt;int&gt; values;// См. ранее</code></p>
<p><code>vector&lt;int&gt; results;</code></p>
<p><code>results.reserve(results.size()+values.size()); // См. ранее</code></p>
<p><code>transform(values.begin(),values.end(), // Результаты вызова</code></p>
<p><code>	back_inserter(results),// transmogrify записываются</code></p>
<p><code>	transmogrify);// в конец вектора results</code></p>
<p><code>// без лишних перераспределений</code></p>
<p><code>// памяти</code></p>
<p>До настоящего момента предполагалось, что алгоритмы (такие как transform) записывают результаты своей работы в контейнер в виде новых элементов. Эта ситуация является наиболее распространенной, но иногда новые данные требуется записать поверх существующих. В таких случаях итератор вставки не нужен, но вы должны в соответствии с данным советом проследить за тем, чтобы приемный интервал был достаточно велик.</p>
<p>Допустим, вызов transform должен записывать результаты в results поверх существующих элементов. Если количество элементов в results не меньше их количества в values, задача решается просто. В противном случае придется либо воспользоваться функцией resize для приведения results к нужному размеру:</p>
<p><code>vector&lt;int&gt; results;</code></p>
<p><code>if ( results.size()&lt;values.size() ){// Убедиться в том, что размер</code></p>
<p><code>	results.resize(values.size());// results по крайней мере</code></p>
<p><code>}// не меньше размера values</code></p>
<p><code>transform(values,begin(),values.end(), // Перезаписать первые</code></p>
<p><code>	back_inserter(results),// values.size() элементов results</code></p>
<p><code>	transmogrify);</code></p>
<p>либо очистить results и затем использовать итератор вставки стандартным способом:</p>
<p><code>results.clear();// Удалить из results все элементы</code></p>
<p><code>results.reserve(values.size());// Зарезервировать память</code></p>
<p><code>transform(values.begin(),values.end(), // Занести выходные данные 	back_inserter(results),// transform в results</code></p>
<p><code>	transmogrify);</code></p>
<p>В данном совете было продемонстрировано немало вариаций на заданную тему, но я надеюсь, что в памяти у вас останется основная мелодия. Каждый раз, когда вы используете алгоритм, требующий определения приемного интервала, позаботьтесь о том, чтобы приемный интервал имел достаточные размеры или автоматически увеличивался во время работы алгоритма. Второй вариант реализуется при помощи итераторов вставки — таких, как <code>ostream_iterator</code>, или возвращаемых в результате вызова back_inserter, front_inserter и inserter. Вот и все, о чем необходимо помнить.</p>
</section>
<section>
<title>
<p>Совет 31. Помните о существовании разных средств сортировки</p>
</title>
<p>Когда речь заходит об упорядочении объектов, многим программистам приходит в голову всего один алгоритм: sort (некоторые вспоминают о qsort, но после прочтения совета 46 они раскаиваются и возвращаются к мыслям о sort).</p>
<p>Действительно, sort — превосходный алгоритм, однако полноценная сортировка требуется далеко не всегда. Например, если у вас имеется вектор объектов Widget и вы хотите отобрать 20 «лучших» объектов с максимальным рангом, можно ограничиться сортировкой, позволяющей выявить 20 нужных объектов и оставить остальные объекты несортированными. Задача называется частичной сортировкой, и для ее решения существует специальный алгоритм partial_sort:</p>
<p><code>bool qualityCompare(const Widgets lhs, const Widgets rhs) {</code></p>
<p><code>	// Вернуть признак сравнения атрибутов quality </code></p>
<p><code>	// объектов lhs и rhs</code></p>
<p><code>}</code></p>
<p><code>partial_sort(widgets.begin(), // Разместить 20 элементов</code></p>
<p><code>	widgets.begin()+20,  // с максимальным рангом</code></p>
<p><code>	widgets.end(), 	// в начале вектора widgets </code></p>
<p><code>	qualityCompare);</code></p>
<p><code>// Использование widgets</code></p>
<p>После вызова partial_sort первые 20 элементов <code>widgets</code> находятся в начале контейнера и располагаются по порядку, то есть <code>widgets [0]</code> содержит Widget с наибольшим рангом, затем следует widgets[l] и т. д.</p>
<p>Если вы хотите выделить 20 объектов Widget и передать их 20 клиентам, но при этом вас не интересует, какой объект будет передан тому или иному клиенту, даже алгоритм partial_sort превышает реальные потребности. В описанной ситуации требуется выделить 20 «лучших» объектов Widget<emphasis><strong> в произвольном порядке. </strong></emphasis>В STL имеется алгоритм, который решает именно эту задачу, однако его имя выглядит несколько неожиданно — он называется <code>nth_element</code>.</p>
<p>Алгоритм nth_element сортирует интервал таким образом, что в заданной вами позиции п оказывается именно тот элемент, который оказался бы в ней при полной сортировке контейнера. Кроме того, при выходе из nth_element ни один из элементов в позициях до п не находится в порядке сортировки после элемента, находящегося в позиции п, а ни один из элементов в позициях после п не предшествует элементу, находящемуся в позиции п. Если такая формулировка кажется слишком сложной, это объясняется лишь тем, что мне приходилось тщательно подбирать слова. Вскоре я объясню причины, но сначала мы рассмотрим пример использования nth_element для перемещения 20 «лучших» объектов Widget в начало контейнера widgets:</p>
<p><code>nth_element(widgets.begin().//Переместить 20 «лучших» элементов</code></p>
<p><code>widgets.beginC)+20,//в начало widgets</code></p>
<p><code>widgets. end(),//в произвольном порядке</code></p>
<p><code>qualityCompare);</code></p>
<p>Как видите, вызов nth_element практически не отличается от вызова partial_sort. Единственное различие заключается в том, что partial_sort сортирует элементы в позициях 1-20, a nth_element этого не делает. Впрочем, оба алгоритма перемещают 20 объектов Widget с максимальными значениями ранга в начало вектора.</p>
<p>Возникает важный вопрос — что делают эти алгоритмы для элементов с одинаковыми значениями атрибута? Предположим, вектор содержит 12 элементов с рангом 1 и 15 элементов с рангом 2. В этом случае выборка 20 «лучших» объектов Widget будет состоять из 12 объектов с рангом 1 и 8 из 15 объектов с рангом 2. Но как алгоритмы partial_sort и nth_element определяют, какие из 15 объектов следует отобрать в «верхнюю двадцатку»? И как алгоритм sort выбирает относительный порядок размещения элементов при совпадении рангов?</p>
<p>Алгоритмы partial sort и nth_element упорядочивают эквивалентные элементы по своему усмотрению, и сделать с этим ничего нельзя (понятие эквивалентности рассматривается в совете 19). Когда в приведенном примере возникает задача заполнения объектами Widget с рангом 2 восьми последних позиций в «верхней двадцатке», алгоритм выберет такие объекты, какие сочтет нужным. Впрочем, такое поведение вполне разумно. Если вы запрашиваете 20 «лучших» объектов Widget, а некоторых объекты равны, то в результате возвращенные объекты будут по крайней мере «не хуже» тех, которые возвращены не были.</p>
<p>Полноценная сортировка обладает несколько большими возможностями. Некоторые алгоритмы сортировки <emphasis><strong>стабильны. </strong></emphasis>При стабильной сортировке два эквивалентных элемента в интервале сохраняют свои относительные позиции после сортировки. Таким образом, если Widget А предшествует Widget В в несортированном векторе widgets и при этом ранги двух объектов совпадают, стабильный алгоритм сортировки гарантирует, что после сортировки Widget А по-прежнему будет предшествовать Widget В. Нестабильный алгоритм такой гарантии не дает.</p>
<p>Алгоритм partial_sort, как и алгоритм nth_element, стабильным не является. Алгоритм sort также не обладает свойством стабильности, но существует специальный алгоритм stable_sort, который, как следует из его названия, является стабильным. При необходимости выполнить стабильную сортировку, вероятно, следует воспользоваться stable_sort. В STL не входят стабильные версии partial_sort и nth_element.</p>
<p>Следует заметить, что алгоритм nth_element чрезвычайно универсален. Помимо выделения n верхних элементов в интервале, он также может использоваться для вычисления медианы по интервалу и поиска значения конкретного <emphasis>процентиля</emphasis> <a l:href="#n3" type="note">[3]</a>:</p>
<p><code>vector&lt;Widget&gt;::iterator begin(widgets.begin()); // Вспомогательные переменные </code></p>
<p><code>vector&lt;Widget&gt;: iterator end(widgets.end()); // для начального и конечного</code></p>
<p><code>// итераторов widgets</code></p>
<p><code>vector&lt;Widget&gt;::iterator goalPosition; // Итератор, указывающий на</code></p>
<p><code>// интересующий нас объект Widget </code></p>
<p><code>// Следующий фрагмент находит Widget с рангом median</code></p>
<p><code>goalPosition = begin + widgets.size()/2; // Нужный объект находится</code></p>
<p><code>// в середине отсортированного  вектора</code></p>
<p><code>nth_element(begin.goalPosition.end. // Найти ранг median в widgets qualityCompare):</code></p>
<p><code>// goalPositon теперь указывает // на Widget с рангом median // Следующий фрагмент находит Widget с уровнем 75 процентилей</code></p>
<p><code>vector&lt;Widget&gt;::size_type goalOffset - // Вычислить удаление нужного 0.25*wdgets.size():// объекта Widget от начала</code></p>
<p><code>nth_element(begin,begin+goalOffset,end, // Найти ранг в</code></p>
<p><code>qualityCompare):// begin+goalOffset теперь</code></p>
<p><code>// указывает на Widget // с уровнем 75 процентилей</code></p>
<p>Алгоритмы <strong>so</strong><emphasis><strong>rt, </strong></emphasis><strong>stable_so</strong><emphasis><strong>rt </strong></emphasis>и <strong>pa</strong><emphasis><strong>rti</strong></emphasis><strong>al_so</strong><emphasis><strong>rt </strong></emphasis>хорошо подходят для упорядочивания результатов сортировки, а алгоритм <strong>nth_element </strong>решает задачу идентификации верхних п элементов или элемента, находящегося в заданной позиции. Иногда возникает задача, близкая к алгоритму <strong>nth_element, </strong>но несколько отличающаяся от него. Предположим, вместо <strong>20 </strong>объектов <strong>Widget </strong>с максимальным рангом потребовалось выделить все объекты <strong>Widget </strong>с рангом 1 или 2. Конечно, можно отсортировать вектор по рангу и затем найти первый элемент с рангом, большим 2. Найденный элемент определяет начало интервала с объектами <strong>Widget, </strong>ранг которых превышает 2.</p>
<p>Полная сортировка потребует большого объема работы, совершенно ненужной для поставленной цели. Более разумное решение основано на использовании алгоритма <strong>partition, </strong>упорядочивающего элементы интервала так, что все элементы, удовлетворяющие заданному критерию, оказываются в начале интервала.</p>
<p>Например, для перемещения всех объектов <strong>Widget </strong>с рангом 2 и более в начало вектора <strong>widgets </strong>определяется функция идентификации:</p>
<p><code>bool hasAcceptableQualty(const Widgets w) {</code></p>
<p><code>// Вернуть результат проверки того, имеет ли объект w ранг больше 2</code></p>
<p><code>}</code></p>
<p>Затем эта функция передается при вызове <strong>partition:</strong></p>
<p><code>vector&lt;Widget&gt;::iterator goodEnd = // Переместить все объекты Widget.</code></p>
<p><code>   partton(widgets.begin(),// удовлетворяющие условию</code></p>
<p><code>	widgets.end()	,// hasAcceptableQuality. в начало</code></p>
<p><code>	hasAcceptableQuality); // widgets и вернуть итератор </code></p>
<p><code>	// для первого объекта. </code></p>
<p><code>	// не удовлетворяющего условию</code></p>
<p>После вызова интервал от <strong>widgets.begin() </strong>до <strong>goodEnd </strong>содержит все объекты <strong>Widget </strong>с рангом 1 и <strong>2, </strong>а интервал от <strong>goodEnd </strong>до <strong>widgets.end(</strong>) содержит все объекты <strong>Widget </strong>с большими значениями ранга. Если бы в процессе деления потребовалось сохранить относительные позиции объектов <strong>Widget </strong>с эквивалентными рангами, вместо алгоритма <strong>partition </strong>следовало бы использовать <strong>stable_partition.</strong></p>
<p>Алгоритмы <strong>sort, stable_sort, partial_so</strong><emphasis><strong>rt </strong></emphasis>и <strong>nth_element </strong>работают с итераторами произвольного доступа, поэтому они применимы только к контейнерам <strong>vector, </strong>string, deque и array. Сортировка элементов в стандартных ассоциативных контейнерах бессмысленна, поскольку эти контейнеры автоматически хранятся в отсортированном состоянии благодаря собственным функциям сравнения. Единственным контейнером, к которому хотелось бы применить алгоритмы sort, stable_sort, partial_sort и nth_element, является контейнер list — к сожалению, это невозможно, но контейнер list отчасти компенсирует этот недостаток функцией sort (интересная подробность: list:.-sort выполняет стабильную сортировку). Таким образом, полноценная сортировка list возможна, но алгоритмы partial_sort и nth_element приходится имитировать. В одном из таких обходных решений элементы копируются в контейнер с итераторами произвольного доступа, после чего нужный алгоритм применяется к этому контейнеру. Другое обходное решение заключается в создании контейнера, содержащего list:: iterator, применении алгоритма к этому контейнеру и последующему обращению к элементам списка через итераторы. Третье решение основано на использовании информации упорядоченного контейнера итераторов для итеративной врезки (splice) элементов list в нужной позиции. Как видите, выбор достаточно широк.</p>
<p>Алгоритмы partition и stable_partition отличаются от sort, stable_sort, partial_ sort и nth_element тем, что они работают только с двусторонними итераторами. Таким образом, алгоритмы partition и stable_partition могут использоваться с любыми стандартными последовательными контейнерами.</p>
<p>Подведем краткий итог возможностей и средств сортировки.</p>
<p>•Полная сортировка в контейнерах vector, string, deque и array: алгоритмы sort и stable_sort.</p>
<p>•Выделение n начальных элементов в контейнерах vector, string, deque и array: алгоритм partial_sort.</p>
<p>•Идентификация n начальных элементов или элемента, находящегося в позиции n, в контейнерах vector, string, deque и array: алгоритм nth_element.</p>
<p>•Разделение элементов стандартного последовательного контейнера на удовлетворяющие и не удовлетворяющие некоторому критерию: алгоритмы partition и stable_partition.</p>
<p>•Контейнер list: алгоритмы partition и stable_partition; вместо sort и stable_sort может использоваться list:: sort. Алгоритмы partial_sort или nth_element приходится имитировать. Существует несколько вариантов их реализации, некоторые из которых были представлены выше.</p>
<p>Чтобы данные постоянно находились в отсортированном состоянии, сохраните их в стандартном ассоциативном контейнере. Стоит также рассмотреть возможность использования стандартного контейнера priority_queue, данные которого тоже хранятся в отсортированном виде (контейнер priority_queue традиционно считается частью STL, но, как упоминалось в предисловии, в моем определении «контейнер STL» должен поддерживать итераторы, тогда как контейнер priority_queue их не поддерживает).</p>
<p>«А что можно сказать о быстродействии?» — спросите вы. Хороший вопрос. В общем случае время работы алгоритма зависит от объема выполняемой работы, а алгоритмам стабильной сортировки приходится выполнять больше работы, чем алгоритмам, игнорирующим фактор стабильности. В следующем списке алгоритмы, описанные в данном совете, упорядочены по возрастанию затрачиваемых ресурсов (времени и памяти):</p>
<table>
<tr>
<td align="left">1.</td>
<td align="left">partition;</td>
</tr>
<tr>
<td align="left">2.</td>
<td align="left">stable_partition;</td>
</tr>
<tr>
<td align="left">3.</td>
<td align="left">nth_element;</td>
</tr>
<tr>
<td align="left">4.</td>
<td align="left">partial_sort;</td>
</tr>
<tr>
<td align="left">5.</td>
<td align="left">sort;</td>
</tr>
<tr>
<td align="left">6.</td>
<td align="left">stable_sort.</td>
</tr>
</table>
<p>При выборе алгоритма сортировки я рекомендую руководствоваться целью, а не соображениями быстродействия. Если выбранный алгоритм ограничивается строго необходимыми функциями и не выполняет лишней работы (например, pa<emphasis><strong>rtiti</strong></emphasis>on вместо полной сортировки алгоритмом so<emphasis><strong>rt), </strong></emphasis>то программа будет не только четко выражать свое предназначение, но и наиболее эффективно решать поставленную задачу средствами STL.</p>
</section>
<section>
<title>
<p>Совет 32. Сопровождайте вызовы remove-подобных алгоритмов вызовом erase</p>
</title>
<p>Начнем с краткого обзора remove, поскольку этот алгоритм вызывает больше всего недоразумений в STL. Прежде всего необходимо рассеять все сомнения относительно того, <emphasis><strong>что </strong></emphasis>делает алгоритм remove, а также <emphasis><strong>почему </strong></emphasis>и <emphasis><strong>как </strong></emphasis>он это делает. Объявление remove выглядит следующим образом:</p>
<p><code>template&lt;class ForwardIterator.class T&gt;</code></p>
<p><code>ForwardIterator remove(ForwardIterator first, ForwardIterator last, const T&amp; value);</code></p>
<p>Как и все алгоритмы, remove получает пару итераторов, определяющих интервал элементов, с которыми выполняется операция. Контейнер при вызове не передается, потому remove не знает, в каком контейнере находятся искомые элементы. Более того, remove не может самостоятельно определить этот контейнер, поскольку не существует способа перехода от итератора к контейнеру, соответствующему ему.</p>
<p>Попробуем разобраться, как происходит удаление элементов из контейнера. Существует только один способ — вызов соответствующей функции контейнера, почти всегда некоторой разновидности erase (контейнер list содержит пару функций удаления элементов, имена которых не содержат erase). Поскольку удаление элемента из контейнера может производиться только вызовом функции данного контейнера, а алгоритм remove не может определить, с каким контейнером он работает, значит, <emphasis><strong>алгоритм </strong></emphasis>remove <emphasis><strong>не может удалять элементы из контейнера. </strong></emphasis>Этим объясняется тот удивительный факт, что вызов <strong>remove </strong>не изменяет количества элементов в контейнере:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>v.reserve(10);</code></p>
<p><code>for(int =l;i&lt;=10;++i){ </code></p>
<p><code>	v.push_back(i);</code></p>
<p><code>};</code></p>
<p><code>// Создать vector&lt;int&gt; и заполнить его </code></p>
<p><code>// числами 1-10 (вызов reserve описан </code></p>
<p><code>// в совете 14)</code></p>
<p><code>cout &lt;&lt; v.size();	// Выводится число 10 </code></p>
<p><code>v[3]=v[5]=v[9]=99; // Присвоить 3 элементам значение 99</code></p>
<p><code>remove(v.begin(),v.end(),99);  // Удалить все элементы со значением 99</code></p>
<p><code>cout &lt;&lt;v. Size();	 // Все равно выводится 10!</code></p>
<p>Чтобы понять смысл происходящего, необходимо запомнить следующее: <emphasis><strong>Алгоритм remove «по настоящему» ничего не удаляет, потому что не может. </strong></emphasis>На всякий случай повторю: <emphasis><strong>...потому что не может!</strong></emphasis></p>
<p>Алгоритм <strong>remove </strong>не знает, из какого контейнера он должен удалять элементы, а без этого он не может вызвать функцию «настоящего» удаления.</p>
<p>Итак, теперь вы знаете, чего алгоритм <strong>remove </strong>сделать не может и по каким причинам. Остается понять, что же он все-таки делает.</p>
<p>В общих чертах <strong>remove </strong>перемещает элементы в заданном интервале до тех пор, пока все «оставшиеся» элементы не окажутся в начале интервала (с сохранением их относительного порядка). Алгоритм возвращает итератор, указывающий на позицию за последним «оставшимся» элементом. Таким образом, возвращаемое значение можно интерпретировать как новый «логический конец» интервала.</p>
<p>В рассмотренном выше примере вектор v перед вызовом <strong>remove </strong>выглядел следующим образом:</p>
<image l:href="#img_10.png"/>
<empty-line/>
<p>Предположим, возвращаемое значение <strong>remove </strong>сохраняется в новом итераторе с именем <strong>newEnd:</strong></p>
<p><code>vector&lt;int&gt;::iterator newEnd(remove(v.begin (),v.end (), 99));</code></p>
<p>После вызова вектор v принимает следующий вид:</p>
<image l:href="#img_11.png"/>
<empty-line/>
<p>Вопросительными знаками отмечены значения элементов, «концептуально» удаленных из v<strong>, </strong>но продолжающих благополучно существовать.</p>
<p>Раз «оставшиеся» элементы v находятся между <strong>v.begin() </strong>и <strong>newEnd, </strong>было бы логично предположить, что «удаленные» элементы будут находиться между <strong>newEnd </strong>и <strong>v.end(). </strong><emphasis><strong>Но это не так\ </strong></emphasis>Присутствие «удаленных» элементов в v вообще не гарантировано. Алгоритм remove не изменяет порядок элементов в интервале так, чтобы «удаленные» элементы сгруппировались в конце — он перемещает «остающиеся» элементы в начало. Хотя в Стандарте такое требование отсутствует, элементы за новым логическим концом интервала обычно <emphasis><strong>сохраняют свои старые значения. </strong></emphasis>Во всех известных мне реализациях после вызова remove вектор v выглядит так:</p>
<image l:href="#img_12.png"/>
<empty-line/>
<p>Как видите, два значения «99», ранее существовавших в v, исчезли, а одно осталось. В общем случае после вызова remove элементы, удаленные из интервала, могут остаться в нем, а могут исчезнуть. Многие программисты находят это странным, но почему? Вы просите remove убрать некоторые элементы, алгоритм выполняет вашу просьбу. Вы же не просили разместить удаленные значения в особом месте для последующего использования... Так в чем проблема? (Чтобы предотвратить потерю значений, вместо remove лучше воспользоваться алгоритмом partition, описанным в совете 31.)</p>
<p>На первый взгляд поведение remove выглядит довольно странно, но оно является прямым следствием принципа работы алгоритма. Во внутренней реализации remove перебирает содержимое интервала и перезаписывает «удаляемые» значения «сохраняемыми». Перезапись реализуется посредством присваивания.</p>
<p>Алгоритм remove можно рассматривать как разновидность уплотнения, при этом удаляемые значения играют роль «пустот», заполняемых в процессе уплотнения. Опишем, что происходит с вектором v из нашего примера.</p>
<p>1.Алгоритм remove анализирует v[0], видит, что данный элемент не должен удаляться, и перемещается к v[1]. То же самое происходит с элементами v[1] и v[2],</p>
<p>2.Алгоритм определяет, что элемент v[3] подлежит удалению, запоминает этот факт и переходит к v[4]. Фактически v[3] рассматривается как «дыра», подлежащая заполнению.</p>
<p>3.Значение v[4] необходимо сохранить, поэтому алгоритм присваивает v[4] элементу v[3], запоминает, что v[4] подлежит перезаписи, и переходит к v[5]. Если продолжить аналогию с уплотнением, элемент v[3] «заполняется» значением v[4], а на месте v[4] образуется новая «дыра».</p>
<p>4.Элемент v[5] исключается, поэтому алгоритм игнорирует его и переходит к v[6]. При этом он продолжает помнить, что на месте v[4] остается «дыра», которую нужно заполнить.</p>
<p>5.Элемент v[6] сохраняется, поэтому алгоритм присваивает v[6] элементу v[4], вспоминает, что следующая «дыра» находится на месте v[5], и переходит к v[7].</p>
<p>6.Аналогичным образом анализируются элементы v[7], v[8] и v[9]. Значение v[7] присваивается элементу v[5], а значение v[8] присваивается элементу v[6]. Элемент v[9] игнорируется, поскольку находящееся в нем значение подлежит удалению.</p>
<p>7.Алгоритм возвращает итератор для элемента, следующего за последним «оставшимся». В данном примере это элемент v[7].</p>
<p>Перемещения элементов в векторе v выглядят следующим образом:</p>
<image l:href="#img_13.png"/>
<p><emphasis><strong></strong></emphasis></p>
<p><emphasis><strong></strong></emphasis></p>
<p>Как объясняется в совете 33, факт перезаписи некоторых удаляемых значений имеет важные последствия в том случае, если эти значения являются указателями. Но в контексте данного совета достаточно понимать, что remove не удаляет элементы из контейнера, поскольку в принципе не может этого сделать. Элементы могут удаляться лишь функциями контейнера, отсюда следует и главное правило настоящего совета: чтобы удалить элементы из контейнера, вызовите erase после remove.</p>
<p>Элементы, подлежащие фактическому удалению, определить нетрудно — это все элементы исходного интервала, начиная с нового «логического конца» интервала и завершая его «физическим» концом. Чтобы уничтожить все эти элементы, достаточно вызвать интервальную форму erase (см. совет 5) и передать ей эти два итератора. Поскольку сам алгоритм remove возвращает итератор для нового логического конца массива, задача решается прямолинейно:</p>
<p><code>vector&lt;int&gt; v;//См. ранее</code></p>
<p><code><strong>v.erase</strong>(remove(v.begin().v.end(),99<strong>),v.end()); </strong>// Фактическое удаление</code></p>
<p><code>// элементов со значением 99 cout &lt;&lt; v.size():</code></p>
<p><code>// Теперь выводится 7</code></p>
<p>Передача в первом аргументе интервальной формы erase возвращаемого значения remove используется так часто, что рассматривается как стандартная конструкция. <code>Remove</code> и erase настолько тесно связаны, что они были объединены в функцию remove контейнера list. Это единственная функция STL с именем remove, которая производит фактическое удаление элементов из контейнера:</p>
<p><code>list&lt;int&gt; li;// Создать список</code></p>
<p><code>// Заполнить данными </code></p>
<p><code>li.remove(99);// Удалить все элементы со значением 99.</code></p>
<p><code>// Команда производит фактическое удаление</code></p>
<p><code>// элементов из контейнера, поэтому размер li</code></p>
<p><code>// может измениться</code></p>
<p>Честно говоря, выбор имени remove в данном случае выглядит непоследовательно. В ассоциативных контейнерах аналогичная функция называется erase, поэтому в контейнере list функцию remove тоже следовало назвать erase. Впрочем, этого не произошло, поэтому остается лишь смириться. Мир, в котором мы живем, не идеален, но другого все равно нет. Как упоминается в совете 44, для контейнеров list вызов функции remove более эффективен, чем применение идиомы erase/remove.</p>
<p>Как только вы поймете, что алгоритм remove не может «по-настоящему» удалять объекты из контейнера, применение его в сочетании с erase войдет в привычку. Не забывайте, что remove — не единственный алгоритм, к которому относится это замечание. Существуют два других remove-подобных алгоритма: remove_if и unique.</p>
<p>Сходство между remove и remove_if настолько прямолинейно, что я не буду на нем останавливаться, но алгоритм unique тоже похож на remove. Он предназначен для удаления смежных повторяющихся значений из интервала без доступа к контейнеру, содержащему элементы интервала. Следовательно, если вы хотите действительно удалить элементы из контейнера, вызов unique должен сопровождаться парным вызовом erase. В контейнере list также предусмотрена функция unique, производящая фактическое удаление смежных дубликатов. По эффективности она превосходит связку erase-unique.</p>
</section>
<section>
<title>
<p>Совет 33. Будьте внимательны при использовании remove-подобных алгоритмов с контейнерами указателей</p>
</title>
<p>Предположим, мы динамически создаем ряд объектов Widget и сохраняем полученные указатели в векторе:</p>
<p><code>class Widget {</code></p>
<p><code>public:</code></p>
<p><code>bool isCertified() const;	// Функция сертификации объектов Widget</code></p>
<p><code>vector&lt;Widget*&gt; v;	// Создать вектор и заполнить его указателями </code></p>
<p><code>v.push_back(new Widget);	// на динамически созданные объекты Widget</code></p>
<p>Поработав с v в течение некоторого времени, вы решаете избавиться от объектов Widget, не сертифицированных функцией Widget, поскольку они вам не нужны. С учетом рекомендаций, приведенных в совете 43 (по возможности использовать алгоритмы вместо циклов), и того, что говорилось в совете 32 о связи remove и erase, возникает естественное желание использовать идиому erase-remove, хотя в данном случае используется алгоритм remove_if:</p>
<p><code>v.erase(remove_if(v.begin(), v.end(),// Удалить указатели на объекты</code></p>
<p><code>	not1(mem_fun(&amp;Widget::isCertified))). //Widget, непрошедшие 	v.end());// сертификацию.</code></p>
<p><code>// Информация о mem_fun </code></p>
<p><code>// приведена в совете 41.</code></p>
<p>Внезапно у вас возникает беспокойство по поводу вызова erase, поскольку вам смутно припоминается совет 7 — уничтожение указателя в контейнере не приводит к удалению объекта, на который он ссылается. Беспокойство вполне оправданное, но в этом случае оно запоздало. Вполне возможно, что к моменту вызова erase утечка ресурсов уже произошла. Прежде чем беспокоиться о вызове erase, стоит обратить внимание на remove_if.</p>
<p>Допустим, перед вызовом remove_if вектор v имеет следующий вид:</p>
<image l:href="#img_14.png"/>
<empty-line/>
<p>После вызова remove_if вектор выглядит примерно так (с итератором, возвращаемым при вызове remove_if):</p>
<image l:href="#img_15.png"/>
<empty-line/>
<empty-line/>
<p>Если подобное превращение кажется непонятным, обратитесь к совету 32, где подробно описано, что происходит при вызове remove (в данном случае — remove_if).</p>
<p>Причина утечки ресурсов очевидна. «Удаленные» указатели на объекты В и С были перезаписаны «оставшимися» указателями. На два объекта Widget не существует ни одного указателя, они никогда не будут удалены, а занимаемая ими память расходуется впустую.</p>
<p>После выполнения remove_if и erase ситуация выглядит следующим образом:</p>
<image l:href="#img_16.png"/>
<empty-line/>
<p>Здесь утечка ресурсов становится особенно очевидной, и к настоящему моменту вам должно быть ясно, почему алгоритмы remove и его аналоги (remove_if и unique) не рекомендуется вызывать для контейнеров, содержащих указатели на динамически выделенную память. Во многих случаях разумной альтернативой является алгоритм partition (см. совет 31).</p>
<p>Если без <strong>remove </strong>никак не обойтись, одно из решений проблемы заключается в освобождении указателей на несертифицированные объекты и присваивании им null перед применением идиомы <strong>erase-remove </strong>с последующим удалением из контейнера всех null-указателей:</p>
<p><code>void delAndNullifyUncertified(Widget*&amp; pWidget) {</code></p>
<p><code>if(!pWidget()-&gt;isCertified()){	//Если объект *pWidget не сертифицирован,</code></p>
<p><code>	delete pWidget;	//удалить указатель</code></p>
<p><code>	pWidget=0;//и присвоить ему null</code></p>
<p><code>}</code></p>
<p><code>for_each(v.begin(),.v.end(),	// Удалить и обнулить  все указатели на </code></p>
<p><code>	delAndNullifyUncertified); // все указатели на объекты, не прошедшие</code></p>
<p><code>v.erase(remove(v.begin(),v.end(), // Удалить из v указатели null;</code></p>
<p><code>	static_cast&lt;Widget*&gt;(0)),  //0 преобразуется в указатель, чтобы  С++</code></p>
<p><code>	v.end());		//правильно определял тип третьего параметра</code></p>
<empty-line/>
<p>Приведенное решение предполагает, что вектор не содержит null-указателей, которые бы требовалось сохранить. В противном случае вам, вероятно, придется написать собственный цикл, который будет удалять указатели в процессе перебора. Удаление элементов из контейнера в процессе перебора связано с некоторыми тонкостями, поэтому перед реализацией этого решения желательно прочитать совет 9.</p>
<p>Если контейнер указателей заменяется контейнером <emphasis><strong>умных </strong></emphasis>указателей с подсчетом ссылок, то все трудности, связанные с <strong>remove, </strong>исчезают, а идиома <strong>erase-remove </strong>может использоваться непосредственно:</p>
<p><code>template&lt;typename Т&gt; class RCSP{..}; // RCSP = "Reference Counting Smart Pointer"</code></p>
<p><code>typedef RCSP&lt;Widget&gt; RCSPW;	// RCSPW = "RCSP to Widget"</code></p>
<p><code>vector&lt;RCSPW&gt; v;</code></p>
<p><code>v.push_back(RCSPW(new Widget)):</code></p>
<p><code>v. erase(remove_if(v.begin() .v.end(),</code></p>
<p><code>not1(mem_fun(&amp;Widget::isCertified))).</code></p>
<p><code>v.end()):</code></p>
<empty-line/>
<p>Чтобы этот фрагмент работал, тип умного указателя (например, <strong>RCSP&lt;Widget</strong><emphasis><strong>&gt;) </strong></emphasis>должен преобразовываться в соответствующий тип встроенного указателя (например <strong>Widget</strong><emphasis><strong>*). </strong></emphasis>Дело в том, что контейнер содержит умные указатели, но вызываемая функция (например <strong>Widget</strong><emphasis><strong>:: </strong><strong>i</strong></emphasis><strong>sCe</strong><emphasis><strong>rti</strong></emphasis><strong>fed) </strong>работает только со встроенными указателями. Если автоматическое преобразование невозможно, компилятор выдаст сообщение об ошибке.</p>
<empty-line/>
<p>Если в вашем программном инструментарии отсутствует шаблон умного указателя с подсчетом ссылок, попробуйте шаблон shared_ptr из библиотеки Boost. Начальные сведения о Boost приведены в совете 50.</p>
<p>Независимо от того, какая методика будет выбрана для работы с контейнерами динамически созданных указателей — умные указатели с подсчетом ссылок, ручное удаление и обнуление указателей перед вызовом remove-подобных алгоритмов или другой способ вашего собственного изобретения — главная тема данного совета остается актуальной: будьте внимательны при использовании remove-подобных алгоритмов с контейнерами указателей. Забывая об этой рекомендации, вы своими руками создаете предпосылки для утечки ресурсов.</p>
</section>
<section>
<title>
<p>Совет 34. Помните о том. какие алгоритмы получают сортированные интервалы</p>
</title>
<p>Не все алгоритмы работают с произвольными интервалами. Например, для алгоритма remove (см. советы 32 и 33) необходимы прямые итераторы и возможность присваивания через эти итераторы. Таким образом, алгоритм не применим к интервалам, определяемым итераторами ввода, а также к контейнерам map/multimap и некоторым реализациям set/multiset (см. совет 22). Аналогично, многие алгоритмы сортировки (см. совет 31) требуют итераторов произвольного доступа и потому не могут применяться к элементам списка.</p>
<p>При нарушении этих правил компилятор выдает длинные, невразумительные сообщения об ошибках (см. совет 49). Впрочем, существуют и другие, более сложные условия. Самым распространенным среди них является то, что некоторые алгоритмы работают только с интервалами <emphasis><strong>отсортированных </strong></emphasis>значений. Данное требование должно неукоснительно соблюдаться, поскольку нарушение приводит не только к выдаче диагностических сообщений компилятора, но и к непредсказуемому поведению программы на стадии выполнения.</p>
<p>Некоторые алгоритмы работают как с сортированными, так и с несортированными интервалами, но максимальную пользу приносят лишь в первом случае. Чтобы понять, почему сортированные интервалы подходят лучше, необходимо понимать принципы работы этих алгоритмов.</p>
<p>Я знаю, что среди читателей встречаются приверженцы «силового запоминания». Ниже перечислены алгоритмы, требующие обязательной сортировки данных:</p>
<p><code><strong>binary_search	lower_bound</strong></code></p>
<p><code><strong>upper_bound	equal_range</strong></code></p>
<p><code><strong>set_union	set_intersection</strong></code></p>
<p><code><strong>set_difference	set_symmetric_difference</strong></code></p>
<p><code><strong>merge	inplace_merge</strong></code></p>
<p><code><strong>includes</strong></code></p>
<p>Кроме того, следующие алгоритмы обычно используются с сортированными интервалами, хотя сортировка и не является обязательным требованием:</p>
<p><code><strong>unique		unique_copy</strong></code></p>
<empty-line/>
<p>Вскоре будет показано, что в определении «сортированный интервал» кроется одно важное ограничение, но сначала позвольте мне немного прояснить ситуацию с этими алгоритмами. Вам будет проще запомнить, какие алгоритмы работают с сортированными интервалами, если вы поймете, для чего нужна сортировка.</p>
<p>Алгоритмы поиска binary_search, lower_bound, upper_bound и equal_range (см. совет 45) требуют сортированные интервалы, потому что их работа построена на бинарном поиске. Эти алгоритмы, как и функция bsearch из библиотеки С, обеспечивают логарифмическое время поиска, но взамен вы должны предоставить им заранее отсортированные значения.</p>
<p>Вообще говоря, логарифмическое время поиска обеспечивается не всегда. Оно гарантировано лишь в том случае, если алгоритмам передаются итераторы произвольного доступа. Если алгоритм получает менее мощные итераторы (например, двусторонние), он выполняет логарифмическое число сравнений, но работает с линейной сложностью. Это объясняется тем, что без поддержки «итераторной математики» алгоритму необходимо линейное время для перемещения между позициями интервала, в котором производится поиск.</p>
<p>Четверка алгоритмов <code>set_unon, set_inesection, set_diffeence</code> и <code>set_symmetric_ difference</code> предназначена для выполнения со множествами операций с линейным временем. Почему этим алгоритмам нужны сортированные интервалы? Потому что в противном случае они не справятся со своей задачей за линейное время. Начинает прослеживаться некая закономерность — алгоритмы требуют передачи сортированных интервалов для того, чтобы обеспечить лучшее быстродействие, невозможное при работе с несортированными интервалами. В дальнейшем мы лишь найдем подтверждение этой закономерности.</p>
<p>Алгоритмы <code>merge</code> и <code>inplace_merge</code> выполняют однопроходное слияние с сортировкой: они читают два сортированных интервала и строят новый сортированный интервал, содержащий все элементы обоих исходных интервалов. Эти алгоритмы работают с линейным временем, что было бы невозможно без предварительной сортировки исходных интервалов.</p>
<p>Перечень алгоритмов, работающих с сортированными интервалами, завершает алгоритм <code>includes</code>. Он проверяет, входят ли все объекты одного интервала в другой интервал. Поскольку <code>includes</code> рассчитывает на сортировку обоих интервалов, он обеспечивает линейное время. Без этого он в общем случае работает медленнее.</p>
<p>В отличие от перечисленных алгоритмов, <code>unique</code> и <code>unique_copy</code> способны работать и с несортированными интервалами. Но давайте взглянем на описание unique в Стандарте (курсив мой): «...Удаляет из каждой <emphasis><strong>смежной </strong></emphasis>группы равных элементов все элементы, кроме первого».</p>
<p>Иначе говоря, если вы хотите, чтобы алгоритм unique удалил из интервала все дубликаты (то есть обеспечил «уникальность» значений в интервале), сначала необходимо позаботиться о группировке всех дубликатов. Как нетрудно догадаться, именно эта задача и решается в процессе сортировки. На практике алгоритм unique обычно применяется для исключения всех дубликатов из интервала, поэтому интервал, передаваемый при вызове unique (или unique_copy), должен быть отсортирован. Программисты Unix могут обратить внимание на поразительное сходство между алгоритмом STL unique и командой Unix uniq — подозреваю, что совпадение отнюдь не случайное.</p>
<p>Следует помнить, что unique исключает элементы из интервала по тому же принципу, что и remove, то есть ограничивается «логическим» удалением. Если вы не совсем уверены в том, что означает этот термин, немедленно обратитесь к советам 32 и 33. Трудно выразить, сколь важно доскональное понимание принципов работы remove и remove-подобных алгоритмов. Общих представлений о происходящем недостаточно. Если вы не <emphasis><strong>знаете, </strong></emphasis>как работают эти алгоритмы, у вас будут неприятности.</p>
<p>Давайте посмотрим, что же означает само понятие «сортированный интервал». Поскольку STL позволяет задать функцию сравнения, используемую в процессе сортировки, разные интервалы могут сортироваться по разным критериям. Например, интервал int можно отсортировать как стандартным образом (то есть по возрастанию), так и с использованием greater&lt;int&gt;, то есть по убыванию. Интервал объектов Widget может сортироваться как по цене, так и по дате. При таком изобилии способов сортировки очень важно, чтобы данные сортировки, находящиеся в распоряжении контейнера STL, была логически согласованы. При передаче сортированного интервала алгоритму, который также получает функцию сравнения, проследите за тем, чтобы переданная функция сравнения вела себя так же, как функция, применявшаяся при сортировке интервала.</p>
<p>Рассмотрим пример неправильного подхода:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>// Создать вектор, заполнить</code></p>
<p><code>// данными, отсортировать</code></p>
<p><code>sort(v.begin(),v.end(),greater&lt;int&gt;0): // по убыванию.</code></p>
<p><code>// Операции с вектором</code></p>
<p><code>// (не изменяющие содержимого).</code></p>
<p><code>bool a5Exists =	// Поиск числа 5 в векторе.</code></p>
<p><code>	binary_search(v.begin(),v.end(),5); // Предполагается, что вектор</code></p>
<p><code>// отсортирован по возрастанию!</code></p>
<p>По умолчанию binary_search предполагает, что интервал, в котором производится поиск, отсортирован оператором &lt; (то есть по возрастанию), но в приведенном примере вектор сортируется по убыванию. Как нетрудно догадаться, вызов binary_search (или lower_bound и т. д.) для интервала, порядок сортировки которого отличен от ожидаемого, приводит к непредсказуемым последствиям.</p>
<p>Чтобы программа работала правильно, алгоритм binary_search должен использовать ту же функцию сравнения, которая использовалась при вызове sort:</p>
<p><code>bool a5Exists = binаry_search(v.begin(),v.end(),5,greater<emphasis><strong>&lt;int&gt;</strong></emphasis>());</code></p>
<p>Все алгоритмы, работающие только с сортированными интервалами (то есть все алгоритмы, упоминавшиеся в данном совете, кроме unique и unique_copy), проверяют совпадение по критерию эквивалентности, как и стандартные ассоциативные контейнеры (которые также сортируются). С другой стороны, unique и unique_copy по умолчанию проверяют совпадение по критерию равенства, хотя при вызове этим алгоритмам может передаваться предикат, определяющий альтернативный смысл «совпадения». За подробной информацией о различиях между равенством и эквивалентностью обращайтесь к совету 19.</p>
<p>Одиннадцать алгоритмов требуют передачи сортированных интервалов для того, чтобы обеспечить повышенную эффективность, невозможную без соблюдения этого требования. Передавайте им только сортированные интервалы, помните о соответствии двух функций сравнения (передаваемой алгоритму и используемой при сортировке) и вы избавитесь от хлопот при проведении поиска, слияния и операций с множествами, а алгоритмы unique и unique_copy будут удалять <emphasis><strong>все </strong></emphasis>дубликаты — чего вы, вероятно, и добивались.</p>
</section>
<section>
<title>
<p>Совет 35. Реализуйте простые сравнения строк без учета регистра символов с использованием mismatch или lexicographical_compare</p>
</title>
<p>Один из вопросов, часто задаваемых новичками в STL — «Как в STL сравниваются строки без учета регистра символов?» Простота этого вопроса обманчива. Сравнения строк без учета регистра символов могут быть очень простыми или очень сложными в зависимости от того, насколько общим должно быть ваше решение. Если игнорировать проблемы интернационализации и ограничиться строками, на которые была рассчитана функция strcmp, задача проста. Если решение должно работать со строками в языках, не поддерживаемых strcmp (то есть практически в любом языке, кроме английского), или программа должна использовать нестандартный локальный контекст, задача чрезвычайно сложна.</p>
<p>В этом совете рассматривается простой вариант, поскольку он достаточно наглядно демонстрирует роль STL в решении задачи (более сложный вариант связан не столько с STL, сколько с проблемами локального контекста, упоминаемыми в приложении А). Чтобы простая задача стала интереснее, мы рассмотрим два возможных решения. Программисты, разрабатывающие интерфейсы сравнения строк без учета регистра, часто определяют два разных интерфейса: первый по аналогии с strcmp возвращает отрицательное число, ноль или положительное число, а второй по аналогии с оператором &lt; возвращает true или false. Мы рассмотрим способы реализации обоих интерфейсов вызова с применением алгоритмов STL.</p>
<p>Но сначала необходимо определить способ сравнения двух символов без учета регистра. Если принять во внимание аспекты интернационализации, задача не из простых. Следующая функция сравнения несколько упрощена, но в данном совете проблемы интернационализации игнорируются, и эта функция вполне подойдет:</p>
<p><code>int ciCharCompare(char c1, char c2) // Сравнение символов без учета {</code></p>
<p><code>// регистра. Функция возвращает -1,</code></p>
<p><code>// если cl&lt;c2, 0, если cl=c2. и 1.</code></p>
<p><code>// если cl&gt;c2.</code></p>
<p><code>int lc1 = tolower(static_cast&lt;unsigned char&gt;(c1));// См. Далее</code></p>
<p><code>int lс2 = tolower(static_cast&lt;unsigned char&gt;(c2));</code></p>
<p><code>if (lc1&lt;lc2) return -1;</code></p>
<p><code>if (lc1&gt;lc2) return 1;</code></p>
<p><code>return 0;</code></p>
<p><code>};</code></p>
<p>Функция <code>ciCharCompare</code> по примеру strcmp возвращает отрицательное число, ноль или положительное число в зависимости от отношения между c1 и с2. В отличие от strcmp, функция <code>ciCharCompare</code> перед сравнением преобразует оба параметра к нижнему регистру. Именно так и достигается игнорирование регистра символов при сравнении.</p>
<p>Параметр и возвращаемое значение функции tolower, как и у многих функций &lt;cctype.h&gt;, относятся к типу int, но эти числа (кроме EOF) должны представляться в виде unsigned char. В С и С++ тип char может быть как знаковым, так и беззнаковым (в зависимости от реализации). Если тип char является знаковым, гарантировать его возможное представление в виде unsigned char можно лишь одним способом: преобразованием типа перед вызовом tolower, этим и объясняется присутствие преобразований в приведенном выше фрагменте (в реализациях с беззнаковым типом char преобразование игнорируется). Кроме того, это объясняет сохранение возвращаемого значения tolower в переменной типа int вместо char.</p>
<p>При наличии <code>chCharCompare</code> первая из двух функций сравнения строк (с интерфейсом в стиле strcmp) пишется просто. Эта функция, <code>ciStringCompare</code>, возвращает отрицательное число, ноль или положительное число в зависимости от отношения между сравниваемыми строками. Функция основана на алгоритме <code>mismatch</code>, определяющем первую позицию в двух интервалах, в которой элементы не совпадают.</p>
<p>Но для вызова mismatch должны выполняться некоторые условия. В частности, необходимо проследить за тем, чтобы более короткая строка (в случае строк разной длины) передавалась в первом интервале. Вся настоящая работа выполняется функцией <code>ciStringCompareImp</code>, а функция <code>ciStringCompare</code> лишь проверяет правильность порядка аргументов и меняет знак возвращаемого значения, если аргументы пришлось переставлять:</p>
<p><code>int ciStringCompareImpl(const string&amp; si, // Реализация приведена далее</code></p>
<p><code>	const string&amp; s2);</code></p>
<p><code>int ciStringCompare(const string&amp; s1,const string&amp; s2) {</code></p>
<p><code>	if (s1.size()&lt;=s2.size() return cStringCompareImpl(s1,s2);</code></p>
<p><code>		else return -ciStringComparelmpl(s2,s1);</code></p>
<p><code>}</code></p>
<p>Внутри <code>ciStringCompareImpl</code> всю тяжелую работу выполняет алгоритм mismatch. Он возвращает пару итераторов, обозначающих позиции первых отличающихся символов в интервалах:</p>
<p><code>int ciStringCompareImpl(const string&amp; si,const string&amp; s2) {</code></p>
<p><code>typedef pair&lt;string::const_iterator,	// PSCI = "pair of</code></p>
<p><code>		string::const_iterator&gt; PSCI;	// string::const_iterator"</code></p>
<p><code>PSCI p = mismatch(		// Использование ptr_fun</code></p>
<p><code>	s1.begin(),s1,end(),	// рассматривается</code></p>
<p><code>	s2.begin(),		// в совете 41</code></p>
<p><code>	not2(ptr_fun(сiCharCompa<emphasis>re)));</emphasis></code></p>
<p><code>if (p.first==s1.end()) { // Если условие истинно,</code></p>
<p><code>	if (p.second==s2.end()) return 0;	 // либо si и s2 равны.</code></p>
<p><code>	else return -1;	// либо s1 короче s2</code></p>
<p><code>}</code></p>
<p><code>return ciCharCompare(*p.first,*p.second); // Отношение между строками }// соответствует отношению</code></p>
<p><code>// между отличающимися</code></p>
<p><code>// символами</code></p>
<p>Надеюсь, комментарии достаточно четко объясняют происходящее. Зная первую позицию, в которой строки различаются, можно легко определить, какая из строк предшествует другой (или же определить, что эти строки равны), В предикате, переданном mismatch, может показаться странной лишь конструкция <code>not2(ptr_fun(ciCharCompare))</code>. Предикат возвращает true для совпадающих символов, поскольку алгоритм mismatch прекращает работу, когда предикат возвращает false. Для этой цели нельзя использовать ciCharCompare, поскольку возвращается -1, 0 или 1, причем по аналогии с strcmp нулевое значение <emphasis><strong>возвращается </strong><strong>для совпадающих символов. </strong></emphasis>Если передать ciCharCompare в качестве предиката для mismatch, С++ преобразует возвращаемое значение ciCharCompare к типу bool, а в этом типе нулю соответствует значение false — результат прямо противоположен тому, что требовалось! Аналогично, когда ciCharCompare возвращает 1 или -1, результат будет интерпретирован как true, поскольку в языке С все целые числа, отличные от нуля, считаются истинными логическими величинами. Чтобы исправить эту семантическую «смену знака», мы ставим not2 и ptr_fun перед ciCharCompare и добиваемся желаемого результата.</p>
<p>Второй вариант реализации ciStringCompare основан на традиционном предикате STL; такая функция может использоваться в качестве функции сравнения в ассоциативных контейнерах. Реализация проста и предельно наглядна, поскольку достаточно модифицировать ciCharCompare для получения функции сравнения символов с предикатным интерфейсом, а затем поручить всю работу по сравнению строк алгоритму lexicographical_compare, занимающему второе место в STL по длине имени:</p>
<p><code>bool ciCharLess(char c1, char c2)// Вернуть признак того,</code></p>
<p><code>{ // предшествует ли c1</code></p>
<p><code>  // символу с2 без учета</code></p>
<p><code>return // регистра. В совете 46</code></p>
<p><code>tolower(static_cast&lt;unsigned char&gt;(c1))&lt; // объясняется, почему</code></p>
<p><code>tolower(static_cast&lt;unsigned char&gt;(c2));	// вместо функции может</code></p>
<p><code>}// оказаться предпочтительным</code></p>
<p><code>// объект функции</code></p>
<empty-line/>
<p><code>bool ciStringCompare(const string&amp; s1. const string&amp; s2) {</code></p>
<p><code>return lexicographical_compare(s1.begin(),s1.end(), // Описание</code></p>
<p><code>		s2.begin(),s2.end(),	 // алгоритма </code></p>
<p><code>		ciCharLess);// приведено далее</code></p>
<p><code>}</code></p>
<p>Нет, я не буду долго хранить секрет. Самое длинное имя у алгоритма set_ symmetric_difference.</p>
<p>Если вы знаете, как работает lexicographical_compare, приведенный выше фрагмент понятен без объяснений, а если не знаете — это легко поправимо.</p>
<p>Алгоритм lexicographical_compare является обобщенной версией strcmp. Функция strcmp работает только с символьными массивами, а lexicographical_compare работает с интервалами значений любого типа. Кроме того, если strcmp всегда сравнивает два символа и определяет отношение между ними (равенство, меньше, больше), то lexicographical_compare может получать произвольный предикат, который определяет, удовлетворяют ли два значения пользовательскому критерию.</p>
<p>В предыдущем примере алгоритм lexicographical_compare должен найти первую позицию, в которой s1 и s2 различаются по критерию ciCharLess. Если для символов в этой позиции ciCharLess возвращает true, то же самое делает и lexicographical_ compare: если в первой позиции, где символы различаются, символ первой строки предшествует соответствующему символу второй строки, то первая строка предшествует второй. Алгоритм lexicographical_compare, как и strcmp, считает два интервала разных величин равными, поэтому для таких интервалов возвращается значение false: первый интервал <emphasis><strong>не предшествует </strong></emphasis>второму. Кроме того, по аналогии с strcmp, если первый интервал завершается до обнаружения различия, lexicographical_compare возвращает true — префикс предшествует любому интервалу, в который он входит.</p>
<p>Довольно о mismatch и lexicographical compare. Хотя в этой книге большое значение уделяется переносимости программ, я просто обязан упомянуть о том, что функции сравнения строк без учета регистра символов присутствуют во многих нестандартных расширениях стандартной библиотеки С. Обычно эти функции называются stricmp или strcmpi и по аналогии с функциями, приведенными в данном совете, игнорируют проблемы интернационализации. Если вы готовы частично пожертвовать переносимостью программы, если строки заведомо не содержат внутренних нуль-символов, а проблемы интернационализации вас не волнуют, то простейший способ сравнения строк без учета регистра символов вообще не связан с STL. Обе строки преобразуются в указатели const char* (см. совет 16), передаваемые при вызове stricmp или strcmpi:</p>
<p><code>int ciStringCompare(const string&amp; si, const string&amp; s2) {</code></p>
<p><code>	return stricmp(sl.c_str().s2.c_str()); // В вашей системе вместо stricmp </code></p>
<p><code>}	// может использоваться другое имя</code></p>
<p>Функции strcmp/strcmp, оптимизированные для выполнения единственной задачи, обычно обрабатывают длинные строки <emphasis><strong>значительно </strong></emphasis>быстрее, чем обобщенные алгоритмы msmatch и lexicographical_compare. Если быстродействие особенно важно в вашей ситуации, переход от стандартных алгоритмов STL к нестандартным функциям С вполне оправдан. Иногда самый эффективный путь использования STL заключается в том, чтобы вовремя понять, что другие способы работают лучше.</p>
<empty-line/>
</section>
<section>
<title>
<p>Совет 36. Правильно реализуйте copy_if</p>
</title>
<empty-line/>
<p>В STL имеется 11 алгоритмов, в именах которых присутствует слово сору:</p>
<p><code><strong>сору		cop_backward</strong></code></p>
<p><code><strong>replace_copy 		 reverse_copy</strong></code></p>
<p><code><strong>replace_copy_if 	 unique_copy</strong></code></p>
<p><code><strong>remove_copy 		 rotate_copy</strong></code></p>
<p><code><strong>remove_copy</strong><strong>_i</strong><strong>f	partial_sort_copy</strong></code></p>
<p><code><strong>uninitialzed_copy</strong></code></p>
<empty-line/>
<p>Но как ни странно, алгоритма copy_if среди них нет. Таким образом, вы можете вызывать replace_copy_if и remove_copy_if, к вашим услугам copy_backward и reverse_copy, но если вдруг потребуется просто скопировать элементы интервала, удовлетворяющие определенному предикату, вам придется действовать самостоятельно.</p>
<p>Предположим, имеется функция для отбора «дефектных» объектов Widget:</p>
<p><code>bool isDefective(const Widget&amp; w);</code></p>
<p>Требуется скопировать все дефектные объекты Widget из вектора в cerr<emphasis><strong>. </strong></emphasis>Если бы алгоритм copy_if существовал, это можно было бы сделать так:</p>
<p><code>vector&lt;Widget&gt; widgets;</code></p>
<p><code>copy_if(widgets.begin(),widgets.end(),// He компилируется -</code></p>
<p><code>	ostream_iterator&lt;Widget&gt;(cerr,"\n").// в STL не существует</code></p>
<p><code>	isDefective);// алгоритма copy_if</code></p>
<p>По иронии судьбы алгоритм copy_if входил в исходную версию STL от Hewlett Packard, которая была заложена в основу библиотеки STL, ставшей частью стандартной библиотеки С++. В процессе сокращения HP STL до размеров, подходящих для стандартизации, алгоритм copy_if остался за бортом.</p>
<p>В книге «The С++ Programming Language» [7] Страуструп замечает, что реализация copy_if выглядит элементарно — и он прав, но это вовсе не означает, что каждый программист сразу придет к нужному решению. Например, ниже приведена вполне разумная версия copy_if, которую предлагали многие программисты (в том числе и я):</p>
<p><code>template&lt;typename InputIterator,// Не совсем правильная</code></p>
<p><code>	typename OutputIterator,// реализация copy_if</code></p>
<p><code>	typename Predicate&gt; </code></p>
<p><code>OutputIterator copy_if(InputIterator begin,</code></p>
<p><code>		InputIterator end,</code></p>
<p><code>		OutputIterator destBegin,</code></p>
<p><code>		Predicate p)</code></p>
<p><code>{</code></p>
<p><code>return remove_copy_if(begin,end,destBegin,not1(p));</code></p>
<p><code>}</code></p>
<p>Решение основано на простом факте: хотя STL не позволяет сказать «скопировать все элементы, для которых предикат равен true», но зато можно потребовать «скопировать все элементы, кроме тех, для которых предикат <emphasis><strong>неравен </strong></emphasis>true». Создается впечатление, что для реализации copy_if достаточно поставить not1 перед предикатом, который должен передаваться copy_if, после чего передать полученный предикат remove_copy_if. Результатом является приведенный выше код.</p>
<p>Если бы эти рассуждения были верны, копирование дефектных объектов Widget можно было бы произвести следующим образом:</p>
<p><code>copy_if(widgets.begin(),.widgets.end(),// Хорошо задумано,</code></p>
<p><code>ostream_iterator&lt;Widget&gt;(cerr,"\n"), // но не компилируется </code></p>
<p><code>isDefective);</code></p>
<p>Компилятор недоволен попыткой применения not1 к isDefective (это происходит внутри copy_if). Как объясняется в совете 41, not1 не может напрямую применяться к указателю на функцию — сначала указатель должен пройти через ptr_fun. Чтобы вызвать эту реализацию copy_if, необходимо передать не просто объект функции, а <emphasis><strong>адаптируемый </strong></emphasis>объект функции. Сделать это несложно, однако возлагать эти хлопоты на будущих клиентов алгоритма STL нельзя. Стандартные алгоритмы STL никогда не требуют, чтобы их функторы были адаптируемыми, поэтому нельзя предъявлять это требование к copy_if. Приведенная выше реализация хороша, но недостаточно хороша.</p>
<p>Правильная реализация copy_if должна выглядеть так:</p>
<p><code>template&lt;typename InputIterator,// Правильная</code></p>
<p><code>	typename OutputIterator,// реализация copy_if</code></p>
<p><code>typename Predicate&gt; OutputIterator copy_if(InputIterator begin.</code></p>
<p><code>InputIterator end, OutputIterator destBegin. Predicate p)</code></p>
<p><code>{</code></p>
<p><code>	while begin!=end) { f(p(*begin)) *destBegn++ = *begin; ++begin;</code></p>
<p><code>}</code></p>
<p><code>return destBegn;</code></p>
<p><code>}</code></p>
<p>Поскольку алгоритм copy_if чрезвычайно полезен, а неопытные программисты STL часто полагают, что он входит в библиотеку, можно порекомендовать разместить реализацию copy_if — правильную реализацию! — в локальной вспомогательной библиотеке и использовать ее в случае надобности.</p>
</section>
<section>
<title>
<p>Совет 37. Используйте accumulate или for_each для обобщения интервальных данных</p>
</title>
<p>Иногда возникает необходимость свести целый интервал к одному числу или, в более общем случае, к одному объекту. Для стандартных задач обобщения существуют специальные алгоритмы. Так, алгоритм count возвращает количество элементов в интервале, а алгоритм count_if возвращает количество элементов, соответствующих заданному предикату. Минимальное и максимальное значение элемента в интервале можно получить при помощи алгоритмов min_element и max_element.</p>
<p>Но в некоторых ситуациях возникает необходимость обработки интервальных данных по нестандартным критериям, и в таких случаях нужны более гибкие и универсальные средства, нежели алгоритмы count, count_if, min_element и max_element. Предположим, вы хотите вычислить сумму длин строк в контейнере, произведение чисел из заданного интервала, усредненные координаты точек и т. д. В каждом из этих случаев производится <emphasis><strong>обобщение </strong></emphasis>интервала, но при этом критерий обобщения вы должны определять самостоятельно. Для подобных ситуаций в STL предусмотрен специальный алгоритм accumulate. Многим программистам этот алгоритм незнаком, поскольку в отличие от большинства алгоритмов он не находится в &lt;algorthm&gt;, а вместе с тремя другими «числовыми алгоритмами» (inner_product, adjacent_difference и partial_sum) выделен в библиотеку &lt;numeric&gt;.</p>
<p>Как и многие другие алгоритмы, accumulate существует в двух формах. Первая форма, получающая пару итераторов и начальное значение, возвращает начальное значение в сумме со значениями из интервала, определяемого итераторами:</p>
<p><code>list&lt;double&gt; ld;// Создать список и заполнить</code></p>
<p><code>// несколькими значениями типа double.</code></p>
<p><code>double sum = accumulate(ld.begin(),ld.end(),0,0); // Вычислить сумму чисел</code></p>
<p><code>// с начальным значением 0.0</code></p>
<p>Обратите внимание: в приведенном примере начальное значение задается в форме 0.0. Эта подробность важна. Число 0.0 относится к типу double, поэтому accumulate использует для хранения вычисляемой суммы переменную типа double. Предположим, вызов выглядит следующим образом:</p>
<p><code>double sum = accumulate(ld.begin(),ld.end(),0): // Вычисление суммы чисел</code></p>
<p><code>// с начальным значением 0; <emphasis>// неправильно!</emphasis></code></p>
<p>В качестве начального значения используется int 0, поэтому accumulate накапливает вычисляемое значение в переменной типа int. В итоге это значение будет возвращено алгоритмом accumulate и использовано для инициализации переменной sum. Программа компилируется и работает, но значение sum будет неправильным. Вместо настоящей суммы списка чисел типа double переменная содержит сумму всех чисел, преобразуемую к int после каждого суммирования.</p>
<p>Алгоритм accumulate работает только с итераторами ввода и поэтому может использоваться даже с istream_iterator и istreambuf_iterator (см. совет 29):</p>
<p><code>cout &lt;&lt; "The sum of the ints on the standard input is " // Вывести сумму </code></p>
<p><code>	&lt;&lt;	accumulate(istream_iterator&lt;int&gt;(cin),// чисел из входного</code></p>
<p><code>	istream_iterator&lt;int&gt;(),// потока</code></p>
<p><code>0);</code></p>
<p>Из-за своей первой, стандартной формы алгоритм accumulate был отнесен к числовым алгоритмам. Но существует и другая, альтернативная форма, которой при вызове передается начальное значение и произвольная обобщающая функция. В этом варианте алгоритм accumulate становится гораздо более универсальным.</p>
<p>В качестве примера рассмотрим возможность применения accumulate для вычисления суммы длин всех строк в контейнере. Для вычисления суммы алгоритм должен знать две вещи: начальное значение суммы (в данном случае 0) и функцию обновления суммы для каждой новой строки. Следующая функция берет предыдущее значение суммы, прибавляет к нему длину новой строки и возвращает обновленную сумму:</p>
<p><code>string::size_type	// См. далее</code></p>
<p><code>stringLengthSum(string::size_type sumSoFar, const string&amp; s)</code></p>
<p><code>{</code></p>
<p><code>	return sumSoFar + s.size();</code></p>
<p><code>}</code></p>
<p>Тело функции убеждает в том, что происходящее весьма тривиально, но на первый взгляд смущают объявления <code>string::size_type</code>. На самом деле в них нет ничего страшного. У каждого стандартного контейнера STL имеется определение типа size_type, относящееся к счетному типу данного контейнера. В частности, значение этого типа возвращается функцией size. Для всех стандартных контейнеров определение size_type должно совпадать с size_t, хотя теоретически нестандартные STL-совместимые контейнеры могут использовать в size_type другой тип (хотя я не представляю, для чего это может понадобиться). Для стандартных контейнеров запись <emphasis><strong>контейнер</strong></emphasis>:: size_type можно рассматривать как специальный синтаксис для size_t.</p>
<p>Функция stringLenghSum является типичным представителем обобщающих функций, используемых при вызове accumulate. Функция получает текущее значение суммы и следующий элемент интервала, а возвращает новое значение накапливаемой суммы. Накапливаемая сумма (сумма длин строк, встречавшихся ранее) относится к типу string::size_type, а обрабатываемый элемент относится к типу string. Как это часто бывает, возвращаемое значение относится к тому же типу, что и первый параметр функции.</p>
<p>Функция stringLenghSum используется в сочетании с accumulate следующим образом:</p>
<p><code>set&lt;string&gt; ss;// Создать контейнер строк</code></p>
<p><code>// и заполнить его данными </code></p>
<p><code>string::size_type lengthSum =// Присвоить lengthSum</code></p>
<p><code>	accumulate(ss.begin(),ss.end(), 	// результат вызова stringLengthSum 	0,stringLengthSum); 	// для каждого элемента ss</code></p>
<p><code>// с нулевым начальным значением</code></p>
<p>Изящно, не правда ли? Произведение вычисляется еще проще, поскольку вместо написания собственной функции суммирования можно обойтись стандартным функтором multiplies:</p>
<p><code>vector&lt;float&gt; vf;// Создать контейнер типа float</code></p>
<p><code>// и заполнить его данными</code></p>
<p><code>float product =// Присвоить product результат</code></p>
<p><code>	accumulate(vf.begin(),vf.end(), // вызова multiplies&lt;float&gt;</code></p>
<p><code>		1.0,multples&lt;float&gt;()); // для каждого элемента vf </code></p>
<p><code>		// с начальным значением 1.0</code></p>
<p>Только не забудьте о том, что начальное значение вместо нуля должно быть равно единице (в вещественном формате, не в int!). Если начальное значение равно нулю, то результат всегда будет равен нулю — ноль, умноженный на любое число, остается нулем.</p>
<p>Последний пример не столь тривиален. В нем вычисляется среднее арифметическое по интервалу точек, представленных структурами следующего вида:</p>
<p><code>struct Point { </code></p>
<p><code>	Point(double initX. double initY):x(initX),y(initY){};</code></p>
<p><code>	double x.y;</code></p>
<p><code>};</code></p>
<p>В этом примере обобщающей функцией будет функтор PointAverage, но перед рассмотрением класса этого функтора стоит рассмотреть его использование при вызове accumulate:</p>
<p><code>list&lt;Point&gt; lp;</code></p>
<p><code>Point avg=</code></p>
<p><code>	accumulate(lp.begin(),lp.end(),</code></p>
<p><code>	Point(<strong>0,0</strong>),</code></p>
<p><code>	PointAverage());</code></p>
<p><code>// Вычисление среднего</code></p>
<p><code>// арифметического по точкам,</code></p>
<p><code>// входящим в список lр</code></p>
<p>Просто и бесхитростно, как и должно быть. На этот раз в качестве начального значения используется объект Point, соответствующий началу координат, а нам остается лишь помнить о необходимости исключения этой точки из вычислений.</p>
<p>Функтор PointAverage отслеживает количество обработанных точек, а также суммы их компонентов х и у. При каждом вызове он обновляет данные и возвращает средние координаты по обработанным точкам. Поскольку для каждой точки в интервале функтор вызывается ровно один раз, он делит суммы по составляющим х и у на количество точек в интервале. Начальная точка, переданная при вызове accumulate, игнорируется.</p>
<p><code>class PointAverage:</code></p>
<p><code>publiс binary_function&lt;Point,Point,Point&gt;{ public:</code></p>
<p><code>PointAverage():xSum(0),ySum(0),numPoints(0) {}</code></p>
<p><code>const Point operator() (const Point&amp; avgSoFar, const Point&amp; p)</code></p>
<p><code>	++numPoints;</code></p>
<p><code>	xSum += p.x;</code></p>
<p><code>	ySum += p.y;</code></p>
<p><code>return Point(xSum/numPoints,ySum/numPoints);</code></p>
<p><code>}</code></p>
<p><code>private:</code></p>
<p><code>size_t numPoints;</code></p>
<p><code>double xSum;</code></p>
<p><code>double ySum;</code></p>
<p>Такое решение прекрасно работает, и лишь из-за периодических контактов с неординарно мыслящими личностями (многие из которых работают в Комитете по стандартизации) я могу представить себе реализации STL, в которых возможны проблемы. Тем не менее, PointAverage нарушает параграф 2 раздела 26.4.1 Стандарта, который, как вы помните, запрещает побочные эффекты по отношению к функции,передаваемой accumulate. Модификация переменных numPoints, xSum и ySum относится к побочным эффектам, поэтому с технической точки зрения приведенный выше фрагмент приводит к непредсказуемым последствиям. На практике трудно представить, что приведенный код может не работать, но чтобы моя совесть была чиста, я обязан специально оговорить это обстоятельство.</p>
<p>Впрочем, у меня появляется удобная возможность упомянуть о for_each — другом алгоритме, который может использоваться для обобщения интервалов. На for_each не распространяются ограничения, установленные для accumulate. Алгоритм for_each, как и accumulate, получает интервал и функцию (обычно в виде объекта функции), вызываемую для каждого элемента в интервале, однако функция, передаваемая for_each, получает только один аргумент (текущий элемент интервала), а после завершения работы for_each возвращает свою функцию (а точнее, ее <emphasis><strong>копию </strong></emphasis>— см. совет 38). Что еще важнее, переданная (и позднее возвращаемая) функция <emphasis><strong>может </strong></emphasis>обладать побочными эффектами.</p>
<p>Помимо побочных эффектов между for_each и accumulate существуют два основных различия. Во-первых, само название accumulate ассоциируется с вычислением сводного значения по интервалу, а название for_each скорее предполагает выполнение некой операции с каждым элементом интервала. Алгоритм for_each может использоваться дя вычисления сводной величины, но такие решения по наглядности уступают accumulate.</p>
<p>Во-вторых, accumulate непосредственно возвращает вычисленное значение, а for_each возвращает объект функции, используемый для дальнейшего получения информации. В С++ это означает, что в класс функтора необходимо включить функцию для получения искомых данных.</p>
<p>Ниже приведен предыдущий пример, в котором вместо accumulate используется for_each:</p>
<p><code>struct Point{...};// См. ранее</code></p>
<p><code>class PointAverage;</code></p>
<p><code>public unary_function&lt;Point,void&gt;{// См. совет 40</code></p>
<p><code>public:</code></p>
<p><code>PointAverage():xSum(0).ySum(0),numPoints(0) {}</code></p>
<p><code>void operator() (const Point&amp; p)</code></p>
<p><code>{</code></p>
<p><code>	++numPoints;</code></p>
<p><code>	xSum += p.x;</code></p>
<p><code>	ySum += p.y:</code></p>
<p><code>}</code></p>
<p><code>Point result() const {</code></p>
<p><code>	return Point(xSum/numPoints,ySum/numPoints);</code></p>
<p><code>}</code></p>
<p><code>private:</code></p>
<p><code>	size t numPoints;</code></p>
<p><code>	double xSum;</code></p>
<p><code>	double ySum;</code></p>
<p><code>};</code></p>
<p><code>list&lt;Point&gt; lp:</code></p>
<p><code>Point avg = for_each(lp.begin(),lp.end(),PointAverage()).result();</code></p>
<p>Лично я предпочитаю обобщать интервальные данные при помощи accumulate, поскольку мне кажется, что этот алгоритм наиболее четко передает суть происходящего, однако foreach тоже работает, а вопрос побочных эффектов для for_each не так принципиален, как для accumulate. Словом, для обобщения интервальных данных могут использоваться оба алгоритма; выберите тот, который вам лучше подойдет.</p>
<p>Возможно, вас интересует, почему у for_each параметр-функция может иметь побочные эффекты, а у accumulate — не может? Представьте, я бы тоже хотел это знать. Что ж, дорогой читатель, некоторые тайны остаются за пределами наших познаний. Чем accumulate принципиально отличается от for_each? Пока я еще не слышал убедительного ответа на этот вопрос.</p>
</section>
</section>
<section>
<title>
<p>Функции, функторы и классы функций</p>
</title>
<section>
<p>Нравится нам это или нет, но функции и представляющие их объекты (функторы) занимают важное место в STL. Они используются ассоциативными контейнерами для упорядочения элементов, управляют работой алгоритмов типа find_if, конструкции for_each и transform без них теряют смысл, а адаптеры типа not1 и bind2nd активно создают их.</p>
<p>Да, функторы и классы функторов встречаются в STL на каждом шагу. Встретятся они и в ваших программах. Умение создавать правильно работающие функторы абсолютно необходимо для эффективного использования STL, поэтому большая часть этой главы посвящена одной теме — как добиться того, чтобы функторы работали именно так, как им положено работать в STL. Впрочем, один совет посвящен другой теме и наверняка пригодится тем, кто задумывался о необходимости включения в программу вызовов ptr_fun, mem_fun и mem_fun_ref. При желании начните с совета 41, но пожалуйста, не останавливайтесь на этом. Когда вы поймете, для чего нужны эти функции, материал остальных советов поможет вам наладить правильное взаимодействие ваших функторов с ними и с STL в целом.</p>
</section>
<section>
<title>
<p>Совет 38. Проектируйте классы функторов для передачи по значению</p>
</title>
<p>Ни С, ни С++ не позволяют передавать функции в качестве параметров других функций. Вместо этого разрешается передавать <emphasis><strong>указатели </strong></emphasis>на функции. Например, объявление стандартной библиотечной функции qsort выглядит следующим образом:</p>
<p><code>void qsort(void *base, size_t nmemb, size_t size,</code></p>
<p><code>int (*cmpfcn)(const void*,const void*));</code></p>
<p>В совете 46 объясняется, почему вместо функции qsort обычно рекомендуется использовать алгоритм sort, но дело не в этом. Нас сейчас интересует объявление параметра <code>cmpfcn</code> функции <code>qsort</code>. При внимательном анализе становится ясно, что аргумент <code>cmpcfn</code>, который является указателем на функцию, копируется (то есть передается по значению) из точки вызова в функцию qsort. Данный пример поясняет правило, соблюдаемое стандартными библиотеками С и С++, — указатели на функции должны передаваться по значению.</p>
<p>Объекты функций STL создавались по образцу указателей на функции, поэтому в STL также действует правило, согласно которому объекты функций передаются по значению (то есть копируются). Вероятно, это правило лучше всего демонстрирует приведенное в Стандарте объявление алгоритма for_each, который получает и передает по значению объекты функций:</p>
<p><code>template&lt;class InputIterator.</code></p>
<p><code>	class Function&gt;</code></p>
<p><code>Functon	// Возврат по значению</code></p>
<p><code>for_each(InputIterator first,</code></p>
<p><code>	InputIterator last,</code></p>
<p><code>	Functon f);// Передача по значению</code></p>
<p>Честно говоря, передача по значению не гарантирована полностью, поскольку вызывающая сторона может явно задать типы параметров в точке вызова. Например, в следующем фрагменте foreach получает и возвращает функторы по ссылке:</p>
<p><code>class DoSomething:</code></p>
<p><code>public unary_function&lt;int,void&gt;{// Базовый класс описан</code></p>
<p><code>void operator() (int x){...}// в совете 40</code></p>
<p><code>};</code></p>
<p><code>typedef deque&lt;int&gt;::iterator DequeIntIter: // Вспомогательное определение</code></p>
<p><code>deque&lt;int&gt; di;</code></p>
<p><code>...</code></p>
<p><code>DoSomething d;	// Создать объект функции</code></p>
<p><code>for_each&lt;DequeIntIter,	//Вызвать for_each с типами</code></p>
<p><code>	DoSomethng&amp;&gt;(di .begin(),//параметров DequelntIter</code></p>
<p><code>		di.end(),//и DoSomething&amp;: в результате</code></p>
<p><code>		d);//происходит передача</code></p>
<p><code>//и возврат по ссылке.</code></p>
<p>Пользователи STL почти никогда не используют эту возможность, а в некоторых реализациях алгоритмов STL при передаче объектов функций по ссылке программы даже не компилируются. В продолжение этого совета будем считать, что объекты функций всегда передаются по значению, поскольку на практике это почти всегда так.</p>
<p>Поскольку объекты функций передаются и возвращаются по значению, вы должны позаботиться о том, чтобы объект функции правильно работал при передаче подобным способом (то есть копированием). Для этого необходимо соблюдение двух условий. Во-первых, объекты функций должны быть небольшими, в противном случае копирование обойдется слишком дорого. Во-вторых, объекты функций должны быть <emphasis><strong>мономорфными </strong></emphasis>(то есть не полиморфными), поэтому в них не могут использоваться виртуальные функции. Второе требование связано с тем, что при передаче по значению объектов производных классов в параметрах базового класса происходит <emphasis><strong>отсечение: </strong></emphasis>в процессе копирования удаляются специализированные составляющие (другой пример проблемы отсечения в STL приведен в совете 3).</p>
<p>Бесспорно, эффективность является важным фактором, и предотвратить отсечение тоже необходимо, однако не все функторы малы и мономорфны. Одно из преимуществ объектов функций перед обычными функциями заключается в отсутствии ограничений на объем информации состояния. Некоторые объекты функций от природы «упитанны», и очень важно, чтобы они могли передаваться алгоритмам STL так же просто, как и их «тощие» собратья.</p>
<p>Столь же нереалистичен и запрет на полиморфные функторы. Иерархическое наследование и динамическое связывание относятся к числу важнейших особенностей С++, и при проектировании классов функторов они могут принести такую же пользу, как и в других областях. Что такое классы функторов без наследования? С++ без «++». Итак, необходимы средства, которые бы позволяли легко передавать большие и/или полиморфные объекты функций с соблюдением установленного в STL правила о передаче функторов по значению.</p>
<p>Такие средства действительно существуют. Достаточно взять данные и/или полиморфные составляющие, которые требуется сохранить в классе функтора, перенести их в другой класс и сохранить в классе функтора указатель на этот новый класс. Рассмотрим пример создания класса полиморфного функтора с большим количеством данных:</p>
<p><code>template&lt;typename Т&gt;	// BPFC = "Big Polymorphic</code></p>
<p><code>class BPFC:	//Functor class"</code></p>
<p><code>public	// Базовый класс описан</code></p>
<p><code>unary_function&lt;T,void&gt; {// в совете 40</code></p>
<p><code>private:</code></p>
<p><code>Widget w;// Класс содержит большой объем</code></p>
<p><code>int х;// данных, поэтому передача</code></p>
<p><code>// по значению</code></p>
<p><code>// была бы неэффективной</code></p>
<p><code>public:</code></p>
<p><code>virtual void operator() (const T&amp; val) const; // Виртуальная функция.</code></p>
<p><code>// создает проблему </code></p>
<p><code>};// отсечения</code></p>
<p>Мы выделяем все данные и виртуальные функции в класс реализации и создаем компактный, мономорфный класс, содержащий указатель на класс реализации:</p>
<p><code>template&lt;typename Т&gt; //Новый класс реализации</code></p>
<p><code>class BPFCImpl{ //для измененного BPFC.</code></p>
<p><code>private:</code></p>
<p><code>Widget w; //Все данные, ранее находившиеся</code></p>
<p><code>int х: //в BPFC, теперь размещаются</code></p>
<p><code>//в этом классе,</code></p>
<p><code>vrtual ~BPFCImpl(); //В полиморфных классах нужен</code></p>
<p><code>//виртуальный деструктор, </code></p>
<p><code>virtual void operator() (const T&amp; val) const;</code></p>
<p><code>friend class BPFC&lt;T&gt;;// Разрешить BPFC доступ к данным</code></p>
<p><code>};</code></p>
<p><code>template&lt;typename T&gt;</code></p>
<p><code>class BPFC:// Компактная, мономорфная версия</code></p>
<p><code>public unary_function&lt;T,void&gt; {</code></p>
<p><code>private:</code></p>
<p><code>	BPFCImpl&lt;T&gt;* pImpl;// Все данные BPFC</code></p>
<p><code>public:</code></p>
<p><code>void operator()(const T&amp; val) const; // Функция не является</code></p>
<p><code>{// виртуальной; вызов передается</code></p>
<p><code>	plImpl-&gt;operator()(val);// BPFCImpl</code></p>
<p><code>}</code></p>
<p><code>};</code></p>
<p>Реализация BFPC:: operator() дает пример того, как должны строиться реализации всех виртуальных функций BPFC: они должны вызывать свои виртуальные «прототипы» из BPFCImpl. Полученный в результате класс функтора (BPFC) компактен и мономорфен, но при этом он предоставляет доступ к большому объему данных состояния и работает полиморфно.</p>
<p>Материал изложен довольно кратко, поскольку описанные базовые приемы хорошо известны в кругах С++. В книге «Effective С++» этой теме посвящен совет 34. В книге «Приемы объектно-ориентированного проектирования» [6] соответствующая методика называется «паттерн Bridge». Саттер в своей книге «Exceptional С++» [8] использует термин «идиома Pimpl».</p>
<p>С позиций STL прежде всего необходимо помнить о том, что классы функторов, использующие данную методику, должны поддерживать соответствующий механизм копирования. Если бы вы были автором приведенного выше класса BPFC, то вам пришлось бы позаботиться о том, чтобы копирующий конструктор выполнял осмысленные действия с объектом BPFCImpl, на который он ссылается. Возможно, простейшее решение заключается в организации подсчета ссылок при помощи указателя shared_ptr из библиотеки Boost или его аналога (см. совет 50).</p>
<p>В сущности, копирующий конструктор BPFC — единственное, о чем вам придется побеспокоиться в контексте данного примера, поскольку при передаче и получении функторов от функций STL всегда происходит копирование (помните, что говорилось выше о передаче по значению?). Из этого вытекают два требования: компактность и мономорфизм.</p>
</section>
<section>
<title>
<p>Совет 39. Реализуйте предикаты в виде «чистых» функций</p>
</title>
<p>Для начала разберемся с основными терминами.</p>
<p><emphasis><strong>Предикатом </strong></emphasis>называется функция, возвращающая тип bool (или другое значение, которое может быть автоматически преобразовано к bool). Предикаты широко используются в STL. В частности, функции сравнения в стандартных ассоциативных контейнерах представляют собой предикаты. Предикатные функции часто передаются в виде параметров таким алгоритмам, как find_if, и различным алгоритмам сортировки (обзор алгоритмов сортировки приведен в совете 31).</p>
<p><emphasis><strong>«Чистой» </strong></emphasis>функцией называется функция, возвращаемое значение которой зависит только от параметров. Если f — «чистая» функция, а х и у — объекты, то возвращаемое значение f (х,у) может измениться только в случае изменения х или у.</p>
<p>В С++ все данные, используемые «чистыми» функциями, либо передаются в виде параметров, либо остаются постоянными на протяжении всего жизненного цикла функции (естественно, такие постоянные данные объявляются с ключевым словом const). Если бы данные, используемые «чистой» функцией, могли изменяться между вызовами, то вызов этой функции в разные моменты времени с одинаковыми параметрами мог бы давать разные результаты, что противоречит определению «чистой» функции.</p>
<p>Из сказанного должно быть понятно, что нужно сделать, чтобы предикаты были «чистыми» функциями. Мне остается лишь убедить читателя в том, что эта рекомендация обоснована. Для этого придется ввести еще один термин.</p>
<p>• <emphasis><strong>Предикатным классом </strong></emphasis>называется класс функтора, у которого функция operator<emphasis><strong>.,</strong></emphasis>) является предикатом, то есть возвращает true или false. Как и следует ожидать, во всех случаях, когда STL ожидает получить предикат, может передаваться либо настоящий предикат, либо объект предикатного класса.</p>
<p>Обещаю, что новых терминов больше не будет. Теперь давайте разберемся, почему следует выполнять рекомендацию данного совета.</p>
<p>В совете 38 объяснялось, что объекты функций передаются по значению, поэтому при проектировании необходимо позаботиться о возможном копировании. Для объектов функций, являющихся предикатами, существует и другой аргумент в пользу специальной поддержки копирования. Алгоритмы могут создавать копии функторов и хранить их определенное время перед применением, причем некоторые реализации алгоритмов этим активно пользуются. Важнейшим следствием этого факта является то, что предикатные функции должны быть «чистыми».</p>
<p>Предположим, вы нарушили это ограничение. Ниже приведен плохо спроектированный класс предиката, который независимо от переданных аргументов возвращает true только один раз — при третьем вызове. Во всех остальных случаях возвращается false.</p>
<p><code>class BadPredicate:	// Базовый класс описан</code></p>
<p><code>public unary_function&lt;Widget.bool&gt;{ // в совете 40</code></p>
<p><code>public:</code></p>
<p><code>BadPredicate():timesCalles(0){}// Переменная timesCalled</code></p>
<p><code>// инициализируется нулем</code></p>
<p><code>bool operator() (const Widget&amp;) {</code></p>
<p><code>	return 	++timesCalled = 3:</code></p>
<p><code>}</code></p>
<p><code>private:</code></p>
<p><code>	size_t timesCalled:</code></p>
<p><code>};</code></p>
<p>Предположим, класс <code>BadPedicate</code> используется для исключения третьего объекта Widget из контейнера vector&lt;Widget&gt;:</p>
<p><code>vector&lt;Widget&gt; vw;// Создать вектор и заполнить его</code></p>
<p><code>// объектами Widget</code></p>
<p><code>vww.erase(remove_if(vw.begin(),	// Удалить третий объект Widget.</code></p>
<p><code>	vw.end(),	// связь между erase и remove_if</code></p>
<p><code>	BadPredcate()),// описана в совете 32</code></p>
<p><code>vw.end());</code></p>
<p>Программа выглядит вполне разумно, однако во многих реализациях STL из вектора vw удаляется не только третий, но и шестой элемент!</p>
<p>Чтобы понять, почему это происходит, необходимо рассмотреть один из распространенных вариантов реализации remove_if. Помните, что эта реализация <emphasis><strong>не является </strong></emphasis>обязательной.</p>
<p><code>template&lt;typename FwdIterator,typename Predicate&gt;</code></p>
<p><code>FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p)</code></p>
<p><code>{</code></p>
<p><code>	begin = find_if(begin,end,p):</code></p>
<p><code>	if(begin==end) return begin;</code></p>
<p><code>	else {</code></p>
<p><code>	FwdIterator next=begin;</code></p>
<p><code>	return remove_copy_if(++next,end,begin,p);</code></p>
<p><code>   }</code></p>
<p><code>}</code></p>
<p>Подробности нас сейчас не интересуют. Обратите внимание: предикат р сначала передается find_if, а затем remove_copy_if. Конечно, в обоих случаях р передается по значению — то есть <emphasis><strong>копируется </strong></emphasis>(теоретически возможны исключения, но на практике дело обстоит именно так; за подробностями обращайтесь к совету 38).</p>
<p>Первый вызов remove_if (расположенный в клиентском коде, удаляющем третий элемент из vw) создает анонимный объект BadPredcate с внутренней переменной timesCalled, равной 0. Этот объект, известный в remove_if под именем р, затем копируется в find_if, поэтому find_if тоже получает объект BadPredicate с переменной timesCalled, равной 0. Алгоритм find_if «вызывает» этот объект, пока тот не вернет true; таким образом, объект вызывается три раза. Затем find_if возвращает управление remove_if. Remove_if продолжает выполняться и в итоге вызывает remove_copy_if, передавая в качестве предиката очередную копию р. Но переменная timesCalled объекта р по-прежнему равна 0! Ведь алгоритм find_if вызывал не р, а лишь <emphasis><strong>копию </strong></emphasis>р. В результате при третьем вызове из remove_copy_if предикат тоже вернет true. Теперь понятно, почему remove_if удаляет два объекта Widget вместо одного.</p>
<p>Чтобы обойти эту лингвистическую ловушку, проще всего объявить функцию operator() с ключевым словом const в предикатном классе. В этом случае компилятор не позволит изменить переменные класса:</p>
<p><code>class BadPredicate:</code></p>
<p><code>public unary_function&lt;Widget.bool&gt; {</code></p>
<p><code>public:</code></p>
<p><code>	bool operator() (const Widget&amp;) const {</code></p>
<p><code>	return ++timesCalled == 3; // Ошибка! Изменение локальных данных</code></p>
<p><code>	}// в константной функции невозможно</code></p>
<p><code>};</code></p>
<p>Из-за простоты этого решения я чуть было не озаглавил этот совет «Объявляйте operator() константным в предикатных классах», но этой формулировки недостаточно. Даже константные функции могут обращаться к mutablе-переменным, неконстантным локальным статическим объектам, неконстантным статическим объектам класса, неконстантным объектам в области видимости пространства имен и неконстантным глобальным объектам. Хорошо спроектированный предикатный класс должен обеспечить независимость функций operator() и от этих объектов. Объявление константных функций operator() в предикатных классах <emphasis><strong>необходимо </strong></emphasis>для правильного поведения, но <emphasis><strong>не достаточно. </strong></emphasis>Правильно написанная функция operator() является константной, но это еще не все. Она должна быть «чистой» функцией.</p>
<p>Ранее в этом совете уже упоминалось о том, что всюду, где STL ожидает получить предикатную функцию, может передаваться либо реальная функция, либо объект предикатного класса. Этот принцип действует в обоих направлениях. В любом месте, где STL рассчитывает получить объект предикатного класса, подойдет и предикатная функция (возможно, модифицированная при помощи ptr_fun — см. совет 41). Теперь вы знаете, что функции operator() в предикатных классах должны быть «чистыми» функциями, поэтому ограничение распространяется и на предикатные функции. Следующая функция также плоха в качестве предиката, как и объекты, созданные на основе класса BadPredcate:</p>
<p><code>bool anotherBadPredicate(const Widgets.const WidgetS) {</code></p>
<p><code>static int timesCalled = 0: // Нет! Нет! Нет! Нет! Нет! Нет! return ++timesCalled == 3: // Предикаты должны быть "чистыми" }// функциями, а "чистые" функции</code></p>
<p><code>// не имеют состояния</code></p>
<p>Как бы вы ни программировали предикаты, они всегда должны быть «чистыми» функциями.</p>
</section>
<section>
<title>
<p>Совет 40. Классы функторов должны быть адаптируемыми</p>
</title>
<p>Предположим, у нас имеется список указателей Widget* и функция, которая по указателю определяет, является ли объект Widget «интересным»:</p>
<p><code>list&lt;Widget*&gt; WidgetPtrs:</code></p>
<p><code>bool isInteresting(const Widget *pw):</code></p>
<p>Если потребуется найти в списке первый указатель на «интересный» объект Widget, это делается легко:</p>
<p><code>list&lt;Widget*&gt;::iterator i =find_if(widgetPts.begin(),widgetPts.end(),</code></p>
<p><code>	isInteesting);</code></p>
<p><code>if (i!=widgetPts.end()) {</code></p>
<p><code>// Обработка первого "интересного"</code></p>
<p><code>}// указателя на Widget</code></p>
<p>С другой стороны, если потребуется найти первый указатель на «неинтересный» объект Widget, следующее очевидное решение не компилируется:</p>
<p><code>list&lt;Widget*&gt;::iterator i = find_if(widgetPtrs.begin(),widgetPtrs.end(),</code></p>
<p><code>	not1(isInteresting));// Ошибка! He компилируется</code></p>
<p>Перед not1 к функции isInteresting необходимо применить ptr_fun:</p>
<p><code>list&lt;Widget*&gt;::iterator i = find_if(widgetPtrs.begin(),widgetPtrs.end(),</code></p>
<p><code>	not1(ptr_fun(isInteresting))); // Нормально</code></p>
<p><code>if (i!=widgetPtrs.end()){</code></p>
<p><code>// Обработка первого </code></p>
<p><code>}// "неинтересного" указателя</code></p>
<p><code>//на Widget</code></p>
<p>При виде этого решения невольно возникают вопросы. <emphasis><strong>Почему </strong></emphasis>мы должны применять ptr_fun к isInteresting перед not1? Что ptr_fun для нас делает и почему начинает работать приведенная выше конструкция?</p>
<p>Ответ оказывается весьма неожиданным. Вся работа ptr_fun сводится к предоставлению нескольких определений типов. Эти определения типов необходимы для not1, поэтому применение not1 к ptr_fun работает, а непосредственное применение not1 к isInteresting не работает. Примитивный указатель на функцию isInteresting не поддерживает определения типов, необходимые для not1.</p>
<p>Впрочем, not1 — не единственный компонент STL, предъявляющий подобные требования. Все четыре стандартных адаптера (not1, not2, bind1st и bind2nd), а также все нестандартные STL-совместимые адаптеры из внешних источников (например, входящие в SGI и Boost — см. совет 50), требуют существования некоторых определений типов. Объекты функций, предоставляющие необходимые определения типов, называются <emphasis><strong>адаптируемыми; </strong></emphasis>при отсутствии этих определений объект называется <emphasis><strong>неадаптируемым. </strong></emphasis>Адаптируемые объекты функций могут использоваться в контекстах, в которых невозможно использование неадаптируемых объектов, поэтому вы должны по возможности делать свои объекты функций адаптируемыми. Адаптируемость не требует никаких затрат, но значительно упрощает использование классов функторов клиентами.</p>
<p>Наверное, вместо туманного выражения «некоторые определения типов» вы бы предпочли иметь точный список? Речь идет об определениях argument_type, first_argument_type, second_argument_type и result_type, но ситуация осложняется тем, что разные классы функторов должны предоставлять разные подмножества этих имен. Честно говоря, если вы не занимаетесь разработкой собственных адаптеров, вам вообще ничего не нужно знать об этих определениях. Как правило, определения наследуются от базового класса, а говоря точнее — от базовой структуры. Для классов функторов, у которых operator() вызывается с одним аргументом, в качестве предка выбирается структура std::unary_function. Классы функторов, у которых operator() вызывается с двумя аргументами, наследуют от структуры std::binary_function.</p>
<p>Впрочем, не совсем так. unary_function и binary_function являются шаблонами, поэтому прямое наследование от них невозможно. Вместо этого при наследовании используются структуры, созданные на основе этих шаблонов, а для этого необходимо указать аргументы типов. Для unary_function задается тип параметра, получаемого функцией operator() вашего класса функтора, а также тип возвращаемого значения. Для binary_function количество типов увеличивается до трех: типы первого и второго параметров operator() и тип возвращаемого значения.</p>
<p>Пара примеров:</p>
<p><code>template&lt;typename Т&gt;</code></p>
<p><code>class MeetsThreshold: public std::unary_function&lt;Widget,bool&gt;{</code></p>
<p><code>private:</code></p>
<p><code>const T threshold; public:</code></p>
<p><code>Meets Threshold(const T&amp; threshold);</code></p>
<p><code>bool operator() (const WidgetS) const;</code></p>
<p><code>};</code></p>
<p><code>struct WidgetNameCompare:</code></p>
<p><code>	std::binary_function&lt;Widget,Widget,bool&gt;{</code></p>
<p><code>	bool operator()(const Widget&amp; lhs,const Widget&amp; rhs) const;</code></p>
<p><code>};</code></p>
<p>В обоих случаях типы, передаваемые unary_function или binary_function, совпадают с типами, получаемыми и возвращаемыми функцией operator() класса функтора, хотя на первый взгляд несколько странно, что тип возвращаемого значения operator() передается в последнем аргументе unary_function или binary_function.</p>
<p>Возможно, вы заметили, что MeetsTheshold является классом, а WidgetNameCompare является структурой. MeetsTheshold обладает внутренним состоянием (переменная threshold), и для инкапсуляции этих данных логично воспользоваться именно классом. WidgetNameCompare состояния не имеет, поэтому и закрытые данные не нужны. Авторы классов функторов, в которых вся информация является открытой, часто объявляют структуры вместо классов — вероятно, только для того, чтобы им не приходилось вводить «public» перед базовым классом и функцией operator(). Выбор между классом и структурой при объявлении таких функторов определяется исключительно стилем программирования. Если вы еще не выработали собственного стиля и стараетесь имитировать профессионалов, учтите, что классы функторов без состояния в самой библиотеке STL (например, less&lt;T&gt;, plus&lt;T&gt; и т. д.) обычно записываются в виде структур.</p>
<p>Вернемся к определению WidgetNameCompare:</p>
<p><code>struct WidgetNameCompare:</code></p>
<p><code>std::binary_function&lt;Widget,Widget.bool &gt;{</code></p>
<p><code>	bool operator()(const Widget&amp; lhs,const Widget&amp; rhs) const;</code></p>
<p><code>};</code></p>
<p>Хотя аргументы operator() относятся к типу const Widget&amp;, шаблону binary_ function передается тип Widget. Обычно при передаче unary_function или binary_function типов, не являющихся указателями, ключевые слова const и знаки ссылки удаляются... только не спрашивайте, почему, — ответ на этот вопрос не интересен и не принципиален. Если вы сгораете от любопытства, напишите программу, в которой они не удаляются, и проанализируйте полученную диагностику компилятора. А если вы <emphasis><strong>и после этого </strong></emphasis>не утратите интерес к этой теме, посетите сайт <a l:href="http://boost.org/">boost.org</a> (см. совет 50) и поищите на нем информацию об адаптерах объектов функций.</p>
<p>Если operator() получает параметры-указатели, ситуация меняется. Ниже приведена структура, аналогичная WidgetNameCompare, но работающая с указателями Widget*:</p>
<p><code>struct PtrWidgetNameCompare:</code></p>
<p><code>std::binary_function<strong>&lt;const Widget*, const Widget*.</strong>bool&gt;{</code></p>
<p><code>	bool operator()<strong>(const Widget* </strong>Ihs. <strong>const Widget* </strong>rhs) const;</code></p>
<p><code>};</code></p>
<p>В этом случае типы, передаваемые binary_function, совпадают с типами, передаваемыми operator(). Общее правило для классов функторов, получающих или возвращающих указатели, заключается в том, что unary_function или binary_ function передаются в точности те типы, которые получает или возвращает operator().</p>
<p>Помните, что базовые классы unary_function и binary_function выполняют только одну важную функцию — они предоставляют определения типов, необходимые для работы адаптеров, поэтому наследование от этих классов порождает адаптируемые объекты функций. Это позволяет использовать в программах следующие конструкции:</p>
<p><code>list&lt;Widget&gt; widgets:</code></p>
<p><code>list&lt;Widget&gt;::reverse_iterator il=//Найти последний объект</code></p>
<p><code>find_if(widgets.rbegin(),widgets.rend(), //Widget, не соответствующий</code></p>
<p><code><strong>	</strong>not1(MeetsThreshold&lt;int&gt;(10))); //пороговому критерию 10</code></p>
<p><code>//(что бы это ни означало)</code></p>
<p><code>Widget  w(<emphasis>аргументы конструктора): // </emphasis>Найти первый объект Widget.</code></p>
<p><code>list&lt;Widget&gt;::iterator i2 =// предшествующий w в порядке</code></p>
<p><code> 	find_if(widgets.begin(),widgets.end(),// сортировки, определенном</code></p>
<p><code>	bind2nd(WidgetNameCompare().w));// WidgetNameCompare</code></p>
<p>Если бы классы функторов не определялись производными от unary_function или binary_function, ни один из этих примеров не компилировался бы, поскольку not1 и bind2nd работают только с адаптируемыми объектами функций.</p>
<p>Объекты функций STL построены по образцу функций С++, а функции С++ характеризуются единственным набором типов параметров и одним типом возвращаемого значения. В результате STL неявно подразумевает, что каждый класс функтора содержит единственную функцию operator(), типы параметров и возвращаемого значения которой должны передаваться unary_function или binary_ function (с учетом правил передачи ссылок и указателей, о которых говорилось ранее). Из этого следует одно важное обстоятельство: не поддавайтесь соблазну и не пытайтесь объединять функциональность WidgetnNameCompare и PtrWidgetCompare в одной структуре с двумя функциями operator(). В этом случае функтор будет адаптируемым по отношению лишь к одной из двух форм вызова (той, что использовалась при передаче параметров binary_function), а пользы от такого решения будет немного — наполовину адаптируемый функтор ничуть не лучше неадаптируемого.</p>
<p>Иногда в классе функтора бывает разумно определить несколько форм вызова, тем самым отказавшись от адаптируемости (примеры таких ситуаций приведены в советах 7, 20, 23 и 25), но это скорее исключение, а не правило. Адаптируемость важна, и о ней следует помнить при разработке классов функторов.</p>
</section>
<section>
<title>
<p>Совет 41. Разберитесь, для чего нужны ptr_fun, mem_fun и mem_fun_ref</p>
</title>
<p>Загадочные функции ptr_fun/mem_fun/mem_fun_ref часто вызывают недоумение. В одних случаях их присутствие обязательно, в других они не нужны... но что же они все-таки делают? На первый взгляд кажется, что они бессмысленно загромождают имена функций. Их неудобно вводить и читать, они затрудняют понимание программы. Что это — очередные пережитки прошлого STL (другие примеры приводились в советах 10 и 18) или синтаксическая шутка, придуманная членами Комитета по стандартизации с извращенным чувством юмора?</p>
<p>Действительно, имена выглядят довольно странно, но функции ptr_fun, mem_fun и mem_fun_ref выполняют важные задачи. Если уж речь зашла о синтаксических странностях, надо сказать, что одна из важнейших задач этих функций связана с преодолением синтаксической непоследовательности С++.</p>
<p>В С++ существуют три варианта синтаксиса вызова функции f для объекта х:</p>
<p><code>f(x);	// Синтаксис 1: f не является функцией класса</code></p>
<p><code>	//(вызов внешней функции)</code></p>
<p><code>x.f();	// Синтаксис 2: f является функцией класса, а х </code></p>
<p><code>// является объектом или ссылкой на объект</code></p>
<p><code>p-&gt;f();	// Синтаксис 3: f является функцией класса,</code></p>
<p><code>//	а р содержит указатель на х</code></p>
<p>Рассмотрим гипотетическую функцию, предназначенную для «проверки» объектов Widget:</p>
<p><code>void test(Widget&amp; w): // Проверить объект w. Если объект не проходит </code></p>
<p><code>// проверку, он помечается как "плохой"</code></p>
<p>Допустим, у нас имеется контейнер объектов Widget:</p>
<p><code>vector&lt;Widget&gt; vw;// vw содержит объекты Widget</code></p>
<p>Для проверки всех объектов Widget в контейнере vw можно воспользоваться алгоритмом for_each:</p>
<p><code>for_each(vw.begin(),vw.end(),test): // Вариант 1 (нормально компилируется)</code></p>
<p>Но представьте, что test является функцией класса Widget, а не внешней функцией (то есть класс Widget сам обеспечивает проверку своих объектов):</p>
<p><code>class Widget { public:</code></p>
<p><code>void test();// Выполнить самопроверку. Если проверка</code></p>
<p><code>// завершается неудачей, объект помечается </code></p>
<p><code>};// как "плохой"</code></p>
<p>В идеальном мире мы могли бы воспользоваться for_each для вызова функции Widget::test всех объектов вектора vw:</p>
<p>for_each(vw.begin(),vw.end(),</p>
<p>	SWidget::test);// Вариант 2 (не компилируется!)</p>
<p>Более того, если бы наш мир был действительно идеальным, алгоритм for_each мог бы использоваться и для вызова Widget::test в контейнере указателей Widget*:</p>
<p><code>list&lt;Widget*&gt; lpw:// Список lpw содержит указатели</code></p>
<p><code>// на объекты Widget</code></p>
<p><code>for_each(lpw.begin(),lpw.end(),</code></p>
<p><code>// Вариант 3 (не компилируется!) Swidget::test);</code></p>
<p>Но подумайте, что должно было бы происходить в этом идеальном мире. Внутри функции for_each в варианте 1 вызывается внешняя функция, поэтому должен использоваться синтаксис 1. Внутри вызова for_each в варианте 2 следовало бы использовать синтаксис 2, поскольку вызывается функция класса. А внутри функции foreach в варианте 3 пришлось бы использовать синтаксис 3, поскольку речь идет о функции класса и указателе на объект. Таким образом, нам понадобились бы <emphasis><strong>три </strong></emphasis>разных версии for_each — разве такой мир можно назвать идеальным?</p>
<p>В реальном мире существует только одна версия for_each. Нетрудно представить себе возможную ее реализацию:</p>
<p><code>template&lt;typename InputIterator.typename Function&gt;</code></p>
<p><code>Function for_each(InputIterator begin. InputIterator end, Function f)</code></p>
<p><code>{</code></p>
<p><code>while (begin!=end) f(*begin++);</code></p>
<p><code>}</code></p>
<p>Жирный шрифт используется для выделения того, что при вызове foreach используется синтаксис 1. В STL существует всеобщее правило, согласно которому функции и объекты функций всегда вызываются в первой синтаксической форме (как внешние функции). Становится понятно, почему вариант 1 компилируется, а варианты 2 и 3 не компилируются — алгоритмы STL (в том числе и for_each) жестко закодированы на использование синтаксиса внешних функций, с которым совместим только вариант 1.</p>
<p>Теперь понятно, для чего нужны функции mem_fun и mem_fun_ref. Они обеспечивают возможность вызова функций классов (обычно вызываемых в синтаксисе 2 и 3) при помощи синтаксиса 1.</p>
<p>Принцип работы mem_fun и mem_fun_ref прост, хотя для пущей ясности желательно рассмотреть объявление одной из этих функций. В действительности они представляют собой шаблоны функций, причем существует несколько вариантов mem_fun и mem_fun_ref для разного количества параметров и наличия-отсутствия константности адаптируемых ими функций классов. Одного объявления вполне достаточно, чтобы разобраться в происходящем:</p>
<p><code>template&lt;typename R, typename C&gt; // Объявление mem_fun для неконстантных </code></p>
<p><code>mem_fun_t&lt;R.C&gt;// функций без параметров. С - класс.</code></p>
<p><code>mem_fun(R(C::*pmf)0);// R - тип возвращаемого значения функции.</code></p>
<p><code>// на которую ссылается указатель</code></p>
<p>Функция mem_fun создает указатель pmf на функцию класса и возвращает объект типа mem_fun_t. Тип представляет собой класс функтора, содержащий указатель на функцию и функцию operator(), которая по указателю вызывает функцию для объекта, переданного operator(). Например, в следующем фрагменте:</p>
<p><code>list&lt;Widget*&gt; lpw;</code></p>
<p><code>// См. ранее</code></p>
<p><code>for_each(lpw.begin(), lpw.end(),</code></p>
<p><code>mem_fun(&amp;Widget::test)); // Теперь нормально компилируется</code></p>
<p>При вызове for_each передается объект типа mem_fun_t, содержащий указатель на Widget:: test. Для каждого указателя Widget* в lpw алгоритм for_each «вызывает» объект mem_fun_t с использованием синтаксиса 1, а этот объект непосредственно вызывает Widget::test для указателя Widget* с использованием синтаксиса 3.</p>
<p>В целом mem_fun приводит синтаксис 3, необходимый для Widget::test при использовании с указателем Widget*, к синтаксису 1, используемому алгоритмом for_ each. По вполне понятным причинам такие классы, как mem_fun_t, называются <emphasis><strong>адаптерами объектов функций. </strong></emphasis>Наверное, вы уже догадались, что по аналогии со всем, о чем говорилось ранее, функции mem_fun_def адаптируют синтаксис 2 к синтаксису 1 и генерируют адаптеры типа mem_fun_left.</p>
<p>Объекты, создаваемые функциями mem_fun и mem_fun_ref, не ограничиваются простой унификацией синтаксиса для компонентов STL. Они (а также объекты, создаваемые функцией ptr_fun) также предоставляют важные определения типов. Об этих определениях уже было рассказано в совете 40, поэтому я не стану повторяться. Тем не менее, стоит разобраться, почему конструкция</p>
<p><code>for_each(vw.begin(),vw.end(),test): // См. ранее, вариант 1.</code></p>
<p><code>// Нормально компилируется</code></p>
<p>компилируется, а следующие конструкции не компилируются:</p>
<p><code>for_each(vw.begin().vw.end(),&amp;Widget::test); //См. ранее, вариант 2.</code></p>
<p><code>// Не компилируется.</code></p>
<p><code>for_each(lpw.begin(),lpw.end(), &amp;Widget::test): //См. ранее, вариант 3.</code></p>
<p><code>//Не компилируется</code></p>
<p>При первом вызове (вариант 1) передается настоящая функция, поэтому адаптация синтаксиса вызова для for_each не нужна; алгоритм сам вызовет ее с правильным синтаксисом. Более того, foreach не использует определения типов, добавляемые функцией ptr_fun, поэтому при передаче test функция ptr_fun не нужна. С другой стороны, добавленные определения не повредят, поэтому следующий фрагмент функционально эквивалентен приведенному выше:</p>
<p><code>for_each(vw.begin(),vw.end<strong>().ptr_fun</strong>(test)): // Компилируется и работает.</code></p>
<p><code>// как вариант 1.</code></p>
<p>Если вы забываете, когда функция ptr_fun обязательна, а в каких случаях без нее можно обойтись, лучше используйте ее при всех передачах функций компонентам STL. STL игнорирует лишние вызовы, и они не отражаются на быстродействии программы. Возможно, во время чтения вашей программы кто-нибудь удивленно поднимет брови при виде лишнего вызова ptr_fun. Насколько это беспокоит вас? Наверное, ответ зависит от природной мнительности.</p>
<p>Существует и другой подход — использовать ptr_fun в случае крайней необходимости. Если функция отсутствует там, где необходимы определения типов, компилятор выдает сообщение об ошибке. Тогда вы возвращаетесь к программе и включаете в нее пропущенный вызов.</p>
<p>С mem_fun и mem_fun_ref ситуация принципиально иная. Эти функции всегда должны применяться при передаче функции компонентам STL, поскольку помимо определения типов (необходимых или нет) они адаптируют синтаксис вызова, который обычно используется для функций класса, к синтаксису, принятому в STL. Если не использовать эти функции при передаче указателей на функции класса, программа не будет компилироваться.</p>
<p>Остается лишь разобраться со странными именами адаптеров. Перед нами самый настоящий пережиток прошлого STL. Когда впервые возникла необходимость в адаптерах, разработчики STL ориентировались на контейнеры указателей (с учетом недостатков таких контейнеров, описанных в советах 7,20 и 33, это может показаться странным, но не стоит забывать, что контейнеры указателей поддерживают полиморфизм, а контейнеры объектов — нет). Когда понадобился адаптер для функций классов (MEMber FUNctions), его назвали <strong>mem_fun. </strong>Только позднее разработчики поняли, что для контейнеров объектов понадобится другой адаптер, и для этой цели изобрели имя <strong>mem_fun_ref. </strong>Конечно, выглядит не слишком элегантно, но... бывает, ничего не поделаешь. Пусть тот, кому никогда не приходилось жалеть о поспешном выборе имен своих компонентов, первым бросит камень.</p>
</section>
<section>
<title>
<p>Совет 42. Следите за тем, чтобы конструкция less&lt;T&gt; означала operator&lt;</p>
</title>
<p>Допустим, объект класса Widget обладает атрибутами weight и maxSpeed:</p>
<p><code>class Widget { public:</code></p>
<p><code>size_t weight() const;</code></p>
<p><code>size_t maxSpeed() const;</code></p>
<p><code>}</code></p>
<p>Будем считать, что естественная сортировка объектов <strong>Widget </strong>осуществляется по атрибуту <strong>weight, </strong>что отражено в операторе &lt; класса <strong>Widget:</strong></p>
<p><code>bool operator&lt;(const Widget&amp; Ihs. const Widget&amp; rhs) {</code></p>
<p><code>return lhs.weight()&lt;rhs.weight();</code></p>
<p><code>}</code></p>
<p>Предположим, потребовалось создать контейнер <strong>multiset&lt;Widget&gt;, </strong>в котором объекты <strong>Widget </strong>отсортированы по атрибуту <strong>maxSpeed. </strong>Известно, что для контейнера <strong>multiset&lt;Widget&gt; </strong>используется функция сравнения <strong>less&lt;Widget&gt;, </strong>которая по умолчанию вызывает функцию <strong>operator&lt; </strong>класса <strong>Widget. </strong>Может показаться, что единственный способ сортировки <strong>multi set&lt;Widget&gt; </strong>по атрибуту <strong>maxSpeed </strong>основан на разрыве связи между less<strong>&lt;Widget&gt; </strong>и <strong>operator&lt; </strong>и специализации less<strong>&lt;Widget&gt; </strong>на сравнении атрибута <strong>maxSpeed:</strong></p>
<p><code>template&lt;&gt;	// Специализация std::less</code></p>
<p><code>struct std::less&lt;Widget&gt;;	// для Widget: такой подход</code></p>
<p><code>public	// считается крайне нежелательным! </code></p>
<p><code>std::binаry_function&lt;Widget,</code></p>
<p><code><strong>		Widget,</strong>// Базовый класс описан</code></p>
<p><code>		bool&gt;{// в совете 40</code></p>
<p><code>bool operator() (const <strong>Widget&amp; </strong>Ihs. const <strong>Widget&amp; </strong>rhs) const</code></p>
<p><code>{</code></p>
<p><code>	return lhs.maxSpeed()&lt;rhs.maxSpeed();</code></p>
<p><code>}</code></p>
<p><code>};</code></p>
<p>Поступать подобным образом не рекомендуется, но, возможно, совсем не по тем причинам, о которых вы подумали. Вас не удивляет, что этот фрагмент вообще компилируется? Многие программисты обращают внимание на то, что в приведенном фрагменте специализируется не обычный шаблон, а шаблон из пространства имен std. «Разве пространство std не должно быть местом священным, зарезервированным для разработчиков библиотек и недоступным для простых программистов? — спрашивают они. — Разве компилятор не должен отвергнуть любое вмешательство в творения бессмертных гуру С++?»</p>
<p>Вообще говоря, попытки модификации компонентов std действительно запрещены, поскольку их последствия могут оказаться непредсказуемыми, но в некоторых ситуациях минимальные изменения все же разрешены. А именно, программистам разрешается специализировать шаблоны std для пользовательских типов. Почти всегда существуют альтернативные решения, но в отдельных случаях такой подход вполне разумен. Например, разработчики классов умных указателей часто хотят, чтобы их классы при сортировке вели себя как встроенные указатели, поэтому специализация std:: less для типов умных указателей встречается не так уж редко. Далее приведен фрагмент класса shared_ptr из библиотеки Boost, упоминающегося в советах 7 и 50:</p>
<p><code>namespace std{</code></p>
<p><code>template&lt;typename T&gt;// Специализация std::less</code></p>
<p><code>struct less&lt;boost::shared_ptr&lt;T&gt; &gt;:// для boost::shared_ptr&lt;T&gt;</code></p>
<p><code>public	// (boost - пространство имен)</code></p>
<p><code>binary_function&lt;boost::shared_ptr&lt;T&gt;,</code></p>
<p><code>		boost::shared_ptr&lt;T&gt;, // Базовый класс описан</code></p>
<p><code>		bool&gt;{// в совете 40</code></p>
<p><code>bool operator() (const boost::shared_ptr&lt;T&gt;&amp; a,</code></p>
<p><code>	const boost::shared_ptr&lt;T&gt;&amp; b) const</code></p>
<p><code>{</code></p>
<p><code>return less&lt;T*&gt;()(a.get(),b.get()): // shared_ptr::get возвращает</code></p>
<p><code>}	// встроенный указатель</code></p>
<p><code>};//из объекта shared_ptr</code></p>
<p><code>}</code></p>
<p>В данном примере специализация выглядит вполне разумно, поскольку специализация less всего лишь гарантирует, что порядок сортировки умных указателей будет совпадать с порядком сортировки их встроенных аналогов. К сожалению, наша специализация less для класса Widget преподносит неприятный сюрприз.</p>
<p>Программисты С++ часто опираются на предположения. Например, они предполагают, что копирующие конструкторы действительно копируют,(как показано в совете 8, невыполнение этого правила приводит к удивительным последствиям). Они предполагают, что в результате взятия адреса объекта вы получаете указатель на этот объект (в совете 18 рассказано, что может произойти в противном случае). Они предполагают, что адаптеры bind1st и not2 могут применяться к объектам функций (см. совет 40). Они предполагают, что оператор + выполняет сложение (кроме объектов string, но знак «+» традиционно используется для выполнения конкатенации строк), что оператор - вычитает, а оператор == проверяет равенство. И еще они предполагают, что функция less эквивалентна operator&lt;</p>
<p>В действительности operator&lt; представляет собой нечто большее, чем реализацию less по умолчанию — он соответствует <emphasis><strong>ожидаемому поведению </strong></emphasis>less. Если less вместо вызова operator&lt; делает что-либо другое, это нарушает ожидания программистов и вступает в противоречие с «принципом минимального удивления». Конечно, поступать так не стоит — особенно если без этого можно обойтись.</p>
<p>В STL нет ни одного случая использования less, когда программисту бы не предоставлялась возможность задать другой критерий сравнения. Вернемся к исходному примеру с контейнером multiset&lt;Widget&gt;, упорядоченному по атрибуту maxSpeed. Задача решается просто: для выполнения нужного сравнения достаточно создать класс функтора практически с любым именем, <emphasis><strong>кроме </strong></emphasis>less. Пример:</p>
<p><code>struct MaxSpeedCompare:</code></p>
<p><code>public binary_function&lt;Widget,Widget,bool&gt; {</code></p>
<p><code>	bool operator()(const Widget&amp; Ihs.const Widget&amp; rhs) const</code></p>
<p><code>	{</code></p>
<p><code>		return lhs,maxSpeed()&lt;rhs.maxSpeed();</code></p>
<p><code>	}</code></p>
<p><code>};</code></p>
<p>При создании контейнера multiset достаточно указать тип сравнения MaxSpeedCompare, тем самым переопределяя тип сравнения по умолчанию (less&lt;Widget&gt;):</p>
<p><code>multiset&lt;Widget,MaxSpeedCompare&gt; widgets;</code></p>
<p>Смысл этой команды абсолютно очевиден: мы создаем контейнер multiset с элементами Widget, упорядоченными в соответствии с классом функтора MaxSpeedCompare. Сравните со следующим объявлением:</p>
<p><code>multiset&lt;Widget&gt; widgets;</code></p>
<p>В нем создается контейнер multiset объектов Widget, упорядоченных по стандартному критерию. Строго говоря, упорядочение производится по критерию less&lt;Widget&gt;, но большинство программистов будет полагать, что сортировка производится функцией operator&lt; Не нужно обманывать их ожидания и подменять определение less. Если вы хотите использовать less (явно или косвенно), проследите за тем, чтобы этот критерий был эквивалентен operator&lt; Если объекты должны сортироваться по другому критерию, создайте специальный класс функтора и назовите его как-нибудь иначе.</p>
<empty-line/>
</section>
</section>
<section>
<title>
<p>Программирование в STL</p>
</title>
<section>
<empty-line/>
<p>STL традиционно характеризуется как совокупность контейнеров, итераторов, алгоритмов и объектов функций, однако <emphasis><strong>программирование </strong></emphasis>в STL заключает в себе нечто большее. Этот термин означает, что программист способен правильно выбирать между циклами, алгоритмами или функциями контейнеров; знает, в каких случаях equal_range предпочтительнее lower_bound, когда lower_bound предпочтительнее find и когда find превосходит equal_range. Термин означает, что программист умеет повышать быстродействие алгоритма посредством замены функций эквивалентными функторами и избегает написания непереносимого или плохо читаемого кода. Более того, к этому понятию даже относится умение читать сообщения об ошибках компилятора, состоящие из нескольких тысяч символов, и хорошее знание Интернет-ресурсов, посвященных STL (документация, расширения и даже полные реализации).</p>
<p>Да, для программирования в STL необходимо много знать, и большая часть этой информации приведена в данной главе.</p>
</section>
<section>
<title>
<p>Совет 43. Используйте алгоритмы вместо циклов</p>
</title>
<p>Каждому алгоритму передается по крайней мере одна пара итераторов, определяющих интервал объектов для выполнения некоторой операции. Так, алгоритм min_element находит минимальное значение в интервале, алгоритм accumulate вычисляет сводную величину, характеризующую интервал в целом (см. совет 37), а алгоритм partition делит элементы интервала на удовлетворяющие и не удовлетворяющие заданному критерию (см. совет 31). Чтобы алгоритм мог выполнить свою задачу, он должен проанализировать каждый объект в переданном интервале (или интервалах), для чего объекты в цикле перебираются от начала интервала к концу. Некоторые алгоритмы (такие как find и find_if) могут вернуть управление до завершения полного перебора, но и в этих алгоритмах задействован внутренний цикл. Ведь даже алгоритмы find и find_if должны проанализировать все элементы интервала, прежде чем принять решение об <emphasis><strong>отсутствии </strong></emphasis>искомого элемента.</p>
<p>Итак, внутренняя реализация алгоритмов построена на использовании циклов. Более того, благодаря разнообразию алгоритмов STL многие задачи, естественно кодируемые в виде циклов, могут решаться при помощи алгоритмов. Рассмотрим класс Widget с функцией redraw():</p>
<p><code>class Widget { public:</code></p>
<p><code>void redraw() const;</code></p>
<p><code>};</code></p>
<p>Если потребуется вызвать функцию redraw для всех объектов в контейнере list, это можно сделать в следующем цикле:</p>
<p><code>list&lt;Widget&gt; lw:</code></p>
<p><code>for(list&lt;Widget&gt;::iterator=lw.begin();i!=lw.end() :++i){ i-&gt;redraw();</code></p>
<p><code>}</code></p>
<p>С другой стороны, с таким же успехом можно воспользоваться алгоритмом for_each:</p>
<p><code>for_each(lw.begin(),lw.end().// Функция mem_fun_ref</code></p>
<p><code>mem_fun_ref(&amp;Widget::redraw)); // описана в совете 41</code></p>
<p>Многие программисты С++ считают, что циклы естественнее алгоритмов, а прочитать цикл проще, чем разбираться в mem_fun_ref и получении адреса Widget:: redraw. Но в заголовке этого совета рекомендуется отдавать предпочтение алгоритмам. В сущности, заголовок означает, что вызов алгоритма предпочтительнее <emphasis><strong>любого </strong></emphasis>явно запрограммированного цикла. Почему?</p>
<p>По трем причинам.</p>
<p>•<strong>Эффективность: </strong>алгоритмы обычно работают эффективнее, чем циклы, организованные программистами.</p>
<p>•<strong>Правильность: </strong>при написании циклов чаще встречаются ошибки, чем при вызове алгоритмов.</p>
<p>•<strong>Удобство сопровождения: </strong>алгоритмы часто порождают более наглядный и прямолинейный код, чем эквивалентные циклы.</p>
<p>Вся оставшаяся часть совета будет посвящена подробному анализу этих причин.</p>
<p>С точки зрения эффективности превосходство алгоритмов объясняется тремя факторами: двумя основными и одним второстепенным. Второстепенный фактор связан с исключением лишних вычислений. Еще раз взгляните на только что приведенный цикл:</p>
<p><code>for (list&lt;Widget&gt;::iterator=lw.begin();i<strong>!=1w.end</strong>():++i){</code></p>
<p><code>i-&gt;redraw();</code></p>
<p><code>}</code></p>
<p>Я выделил условие завершения цикла, чтобы подчеркнуть, что при каждой итерации цикла будет выполнено сравнение с lw.end(). Следовательно, при каждой итерации будет вызываться функция list:: end. Однако вызывать эту функцию больше одного раза не нужно, поскольку цикл не модифицирует список. Но если взглянуть на вызов алгоритма, можно заметить, что end вызывается ровно один раз:</p>
<p><code>for_each(lw.begin(),lw.end(),	// lw.end() вычисляется</code></p>
<p><code>mem_fun_ref(&amp;Widget::redraw)); // только один раз</code></p>
<p>Объективности ради замечу: авторы реализаций STL хорошо понимают, что функции begin и end (и другие функции — например, size) используются очень часто, и стараются оптимизировать их с расчетом на максимальную эффективность. Они почти всегда объявляют такие функции подставляемыми (inline) и стараются кодировать их так, чтобы большинство компиляторов могло избежать повторяющихся вычислений, выводя результаты из цикла. Впрочем, опыт показывает, что это не всегда им удается, и в таких случаях исключения повторяющихся вычислений вполне достаточно, чтобы алгоритмы имели преимущество по быстродействию перед циклами, закодированными вручную.</p>
<p>Но как было сказано выше, вывод лишних вычислений из цикла является второстепенным фактором, существуют два более важных. Первый важный фактор заключается в том, что разработчики библиотек могут воспользоваться знанием внутренней реализации контейнера и оптимизировать перебор так, как не сможет ни один пользователь библиотеки. Например, объекты во внутреннем представлении контейнера deque обычно хранятся в одном или нескольких массивах фиксированного размера. Перебор в этих массивах с использованием указателей производится быстрее, чем перебор на базе итераторов, однако он может использоваться только разработчиками библиотеки, поскольку они знают размер внутренних массивов и способ перехода от одного массива к другому. Некоторые версии STL содержат реализации алгоритмов, использующие внутренние структуры данных deque; эксперименты показали, что они работают примерно на 20% быстрее «обычных» реализаций.</p>
<p>Здесь важно не то, что реализации STL оптимизируются для deque (или другого конкретного типа контейнера), а то, что разработчики знают об устройстве контейнеров больше, чем простые пользователи, и могут применить свои знания при реализации алгоритмов. Отказываясь от алгоритмов в пользу циклов, вы не сможете пользоваться преимуществами оптимизации, основанной на знании внутреннего устройства структур данных.</p>
<p>Второй принципиальный аргумент заключается в том, что практически все алгоритмы STL (кроме самых элементарных) основаны на теоретических разработках, более сложных — а иногда <emphasis><strong>гораздо </strong></emphasis>более сложных, — нежели те, которые может предложить средний программист С++. Превзойти sort и его сородичей (см. совет 31) по эффективности практически невозможно; столь же эффективны алгоритмы поиска в сортированных интервалах (см. советы 34 и 45). Даже повседневные задачи вроде удаления объектов из блоковых контейнеров более эффективно решаются при помощи идиомы erase-remove, чем при помощи самостоятельно запрограммированных циклов (см. совет 9).</p>
<p>Если соображений эффективности недостаточно, существует и другой принципиальный фактор — правильность работы программы. В частности, при самостоятельном программировании циклов приходится следить за тем, чтобы итераторы (1) были действительными и (2) указывали на те элементы, на которые они должны указывать. Предположим, у нас имеется массив (возможно, из-за использования унаследованного интерфейса с языком С — см. совет 16), и вы хотите взять каждый элемент массива, прибавить к нему 41 и вставить в начало контейнера deque. При самостоятельном программировании цикла примерная реализация выглядит приблизительно так (следующий фрагмент представляет собой видоизмененный пример из совета 16):</p>
<p><code>// Функция получает указатель на массив.</code></p>
<p><code>// содержащий не более arraySize чисел типа double,</code></p>
<p><code>// и записывает в него данные.</code></p>
<p><code>// Возвращается количество записанных чисел.</code></p>
<p><code>size_t fillArray(double *pArray, size_t arraySize);</code></p>
<p><code>double data[maxNumDoubles]; // Определение локального массива</code></p>
<p><code>deque&lt;double&gt; d;// Создать контейнер deque</code></p>
<p><code>// и заполнить его данными</code></p>
<p><code>size_t numDoubles = fillArray(data.maxNumDoubles): // Получение данных от функции</code></p>
<p><code>for (size_t i=0;i&lt;numDoubles;++i){ //Для каждого индекса i в data</code></p>
<p><code>d.insert(d.begin(),data[i]+41); //вставить в начало d значение</code></p>
<p><code>}//data[i]+41.</code></p>
<p><code>//Программа содержит ошибку!</code></p>
<p>Вообще говоря, этот пример работает — если вас устраивает, что вновь вставленные элементы следуют в порядке, обратном порядку соответствующих элементов data. Вставка производится в позиции d. begin(), поэтому последний вставленный элемент попадает в начало контейнера!</p>
<p>Если изменение порядка не было предусмотрено (признайтесь, ведь не было!), проблему можно решить следующим образом:</p>
<p><code>deque&lt;double&gt;:: iterator insertLocaton = d.begin(); // Сохранить итератор</code></p>
<p><code>	// для начальной</code></p>
<p><code>	// позиции d</code></p>
<p><code>for (size_t =0;i&lt;numDoubles;++i){ // Вставить значение data[i]+41</code></p>
<p><code>	d.insert(insertLocaton++,data[i]+41); // в позиции insertLocation </code></p>
<p><code>}// и увеличить insertLocation.</code></p>
<p><code>// Программа также содержит ошибку!</code></p>
<p>На первый взгляд кажется, что этот фрагмент решает сразу две проблемы — программа не только наращивает итератор, задающий позицию вставки, но и избавляется от необходимости заново вычислять begin при каждой итерации; тем самым решается второстепенная проблема повторяющихся вычислений, о которой говорилось выше. К сожалению, вместо этих двух проблем возникает третья — программа вообще перестает работать. При каждом вызове deque::insert все итераторы deque, включая insertLocation, становятся недействительными, поэтому второй и все последующие вызовы insert приводят к непредсказуемым последствиям.</p>
<p>После обнаружения этой проблемы (возможно, при помощи отладочного режима STL — см. совет 50) приходит в голову следующее решение:</p>
<p><code>deque&lt;double&gt;::iterator insertLocation = d.begin():// См. ранее</code></p>
<p><code>for (size_t i=0;i&lt;numDoubles;++i){// Программа обновляет</code></p>
<p><code>	insertLocaton=	// итератор insertLocation</code></p>
<p><code>	d.insert(insertLocaton,data[i]+41); // при каждом вызове insert</code></p>
<p><code>	++insertLocation;	// и увеличивает его.</code></p>
<p><code>}</code></p>
<p>Программа делает именно то, что требовалось, но подумайте, как много времени понадобилось, чтобы прийти к верному решению! А теперь сравните со следующим вызовом transform:</p>
<p><code>transform(data,data+numDoubles,// Копирование всех элементов</code></p>
<p><code>	inserter(d,d.begin()),// из data в начало d</code></p>
<p><code>	bind2nd(plus&lt;int&gt;(),41)); // с прибавлением 41</code></p>
<p>Возможно, вам потребуется пара минут на анализ конструкции bnd2nd(plus &lt;int&gt;(),41), но после этого все хлопоты с итераторами сводятся к простому заданию начала и конца исходного интервала и вызову inserter при определении начала приемного интервала (см. совет 30). На практике итераторы исходного и приемного интервала обычно вычисляются относительно просто — во всяком случае, это значительно проще, чем диагностика случайного появления недействительных итераторов в теле цикла.</p>
<p>Данный пример убедительно показывает, что программирование циклов часто бывает связано с трудностями. Программисту приходится постоянно следить за тем, чтобы итераторы в процессе цикла не стали недействительными или с ними не были выполнены недопустимые операции. Другой пример скрытого перехода итераторов в недействительное состояние приведен при описании циклических вызовов erase в совете 9.</p>
<p>Применение недействительных итераторов приводит к непредсказуемым последствиям, которые редко проявляются на стадии разработки и тестирования. Так зачем идти на риск, если без этого можно обойтись? Поручите работу алгоритмам, пусть <emphasis><strong>они </strong></emphasis>беспокоятся о технических подробностях операций с итераторами.</p>
<p>Итак, я объяснил, почему алгоритмы обычно работают эффективнее «ручных» циклов и почему при работе с циклами возникают многочисленные трудности, отсутствующие при использовании алгоритмов. Если мне повезло, вы поверили в силу алгоритмов, но везение — вещь ненадежная, а я хочу окончательно разобраться в этом вопросе перед тем, как следовать дальше. Мы переходим к следующему фактору: наглядности кода. В долгосрочной перспективе принцип наглядности очень важен, поскольку наглядную программу проще понять, она проще усовершенствуется, сопровождается и адаптируется в соответствии с новыми требованиями. Циклические конструкции выглядят привычнее, но алгоритмы обладают значительными преимуществами.</p>
<p>Одним из ключевых преимуществ является семантическая сила стандартных имен. В STL существует 70 имен алгоритмов, с учетом перегрузки (overloading) получается более 100 различных шаблонов функций. Каждый алгоритм выполняет четко определенную задачу, <emphasis><strong>и вполне логично ожидать, что профессиональный программист С++ знает эти задачи (или легко найдет нужную информацию). </strong></emphasis>Таким образом, при виде вызова transform программист понимает, что некоторая функция применяется ко всем объектам в интервале, а результат куда-то записывается. При виде вызова replace_if он знает, что программа модифицирует все объекты интервала, удовлетворяющие некоторому предикату. Вызов partition наводит на мысль о том, что объекты интервала перемещаются с группировкой всех объектов, удовлетворяющих предикату (см. совет 31). Имена алгоритмов STL несут большую семантическую нагрузку и более четко выражают смысл происходящего, чем любые циклы.</p>
<p>При виде цикла for, while и do программист знает только одно — программа многократно выполняет некоторые действия. Чтобы получить хотя бы примерное представление о происходящем, необходимо изучить тело цикла. С алгоритмами дело обстоит иначе, сам вызов алгоритма характеризует суть происходящего. Конечно, для полноценного понимания необходимо проанализировать аргументы, передаваемые алгоритму, но обычно это требует меньшей работы, чем анализ обобщенной циклической конструкции.</p>
<p>Проще говоря, имена алгоритмов информативны, а ключевые слова for, while или do — нет. Впрочем, это относится практически ко всем компонентам стандартных библиотек С и С++. Никто не запрещает вам написать собственную реализацию strlen, memset или bsearch, но вы этого не делаете. Почему? Во-первых, кто-то уже сделал это за вас, и нет смысла повторять уже выполненную работу; во-вторых, имена этих функций стандартны, и все знают, что они делают; в-третьих, можно предположить, что автор библиотеки знает приемы оптимизации, недоступные для вас, и отказываться от возможного повышения эффективности было бы неразумно. А раз вы не пишете собственные версии strlen и т. д., то было бы нелогично программировать циклы, дублирующие функциональность готовых алгоритмов STL.</p>
<p>На этом я бы хотел завершить данный совет, поскольку финал выглядит довольно убедительно. К сожалению, тема не поддается столь однозначной трактовке.</p>
<p>Действительно, имена алгоритмов информативнее простых циклов, но четкая формулировка действий, выполняемых при каждой итерации, иногда бывает нагляднее вызова алгоритма. Допустим, нам потребовалось найти первый элемент вектора, значение которого лежит в заданном диапазоне &lt;х,у&gt;. В цикле это делается так:</p>
<p><code>vector&lt;int&gt; v;</code></p>
<p><code>int х,у:</code></p>
<p><code>vector&lt;int&gt;::iterator i=v.begin(); //Перебирать элементы, начиная</code></p>
<p><code>for(;i!=v.end();++i){//с v.begin(). до нахождения нужного</code></p>
<p><code>if(*i&gt;x&amp;&amp;*i&lt;y)) break://элемента или достижения v.end()</code></p>
<p><code>}</code></p>
<p><code>//После завершения цикла</code></p>
<p><code>//i указывает на искомый элемент</code></p>
<p><code>//или совпадает с v.end()</code></p>
<p>То же самое можно сделать и при помощи find_if, но для этого придется воспользоваться нестандартным адаптером объекта функции — например, compose2 из реализации SGI (см. совет 50):</p>
<p><code>vector&lt;int&gt;::iterator i =</code></p>
<p><code>find_if(v.begin(), v.end(),	// Найти первое значение <emphasis>val.</emphasis></code></p>
<p><code>	compose2(logical_and&lt;bool&gt;0, // для которого одновременно </code></p>
<p><code>		bind2nd(greater&lt;int&gt;(),x).// истинны условия </code></p>
<p><code>		bind2nd(less&lt;int&gt;(),y))): // <emphasis>val&gt;x. </emphasis>и <emphasis>val&lt;y</emphasis></code></p>
<p>Но даже если бы нестандартные компоненты не использовались, многие программисты полагают, что вызов алгоритма значительно уступает циклу по наглядности, и я склонен с ними согласиться (см. совет 47).</p>
<p>Вызов <strong>find_if </strong>можно было бы упростить за счет выделения логики проверки в отдельный класс функтора.</p>
<p><code>template&lt;typename Т&gt;</code></p>
<p><code>class BetweenValues:</code></p>
<p><code>public unary_function&lt;T,bool&gt;{// См. совет 40</code></p>
<p><code>public:</code></p>
<p><code>	BetweenValues(const T&amp; lowValue, const T&amp; highValue)</code></p>
<p><code>:lowVal(lowValue),highVal(highValue) {}</code></p>
<p><code>bool operator() (const T&amp; val) const </code></p>
<p><code>{</code></p>
<p><code>	return val&gt;lowVal&amp;&amp;val&lt;highVal;</code></p>
<p><code>}</code></p>
<p><code>private: </code></p>
<p><code>	T lowVal;</code></p>
<p><code>	T highVal;</code></p>
<p><code>};</code></p>
<p><code>vector&lt;int&gt; iterator i = find_if(v.begin().v.end(),</code></p>
<p><code>	BetweenValues<emphasis><strong>&lt;int&gt;</strong></emphasis>(x,y));</code></p>
<p>Однако у такого решения имеются свои недостатки. Во-первых, создание шаблона <strong>BetweenValues </strong>требует значительно большей работы, чем простое написание тела цикла. Достаточно посчитать строки в программе: тело цикла — одна строка, <strong>BetweenValues </strong>— четырнадцать строк. Соотношение явно не в пользу алгоритма. Во-вторых, описание критерия поиска физически отделяется от вызова. Чтобы понять смысл вызова <strong>find_if, </strong>необходимо найти определение <strong>BetweenValues, </strong>но оно должно располагаться вне функции, содержащей вызов <strong>find_if</strong>. Попытка объявить <strong>BetweenValues </strong><emphasis><strong>внутри </strong></emphasis>функции, содержащей вызов <strong>find_if:</strong></p>
<p><code>{// Начало функции</code></p>
<p><code>template&lt;typename Т&gt;</code></p>
<p><code>class BetweenValues:public unary_function&lt;T.bool&gt; {...4}</code></p>
<p><code>vector&lt;int&gt;::iterator i = find_if(v.begin(), v.end(),</code></p>
<p><code>	BetweenVa1ues&lt;int&gt;(x,у));</code></p>
<p><code>};// Конец функции</code></p>
<p>не компилируется, поскольку шаблоны не могут объявляться внутри функций. Если попробовать обойти это ограничение посредством реализации <strong>BetweenValues </strong>в виде класса:</p>
<p>{// Начало функции</p>
<p>class BetweenValues:public unary_function&lt;int,bool&gt; {...}</p>
<p>vector&lt;int&gt;: iterator i = find_if(v.begin(). v.end().</p>
<p>BetweenValues(x.y));</p>
<p>}:// Конец функции</p>
<p>все равно ничего не получается, поскольку классы, определяемые внутри функций, являются <emphasis><strong>локальными, </strong></emphasis>а локальные классы не могут передаваться в качестве аргументов шаблонов (как функтор, передаваемый find_if). Печально, но классы функторов и шаблоны классов функторов не разрешается определять внутри функций, как бы удобно это ни было.</p>
<p>В контексте борьбы между вызовами алгоритмов и циклами это означает, что выбор определяется исключительно содержимым цикла. Если алгоритм уже умеет делать то, что требуется, или нечто очень близкое, вызов алгоритма более нагляден. Если задача элементарно решается в цикле, а при использовании алгоритма требует сложных нагромождений адаптеров или определения отдельного класса функтора, вероятно, лучше ограничиться циклом. Наконец, если в цикле приходится выполнять очень длинные и сложные операции, выбор снова склоняется в пользу алгоритмов, потому что длинные и сложные операции лучше оформлять в отдельных функциях. После того как тело цикла будет перенесено в отдельную функцию, почти всегда удается передать эту функцию алгоритму (особенно часто — алгоритму for_each) так, чтобы полученный код был более наглядным и прямолинейным.</p>
<p>Если вы согласны с тем, что вызовы алгоритмов обычно предпочтительнее циклов, а также с тем, что интервальные функции обычно предпочтительнее циклического вызова одноэлементных функций (см, совет 5), можно сделать интересный вывод: хорошо спроектированная программа С++, использующая STL, содержит гораздо меньше циклических конструкций, чем аналогичная программа, не использующая STL, и это хорошо. Замена низкоуровневых конструкций <code>for</code>, <code>while</code> и <code>do</code> высокоуровневыми терминами insert, find и foreach повышает уровень абстракции и упрощает программирование, документирование, усовершенствование и сопровождение программы.</p>
</section>
<section>
<title>
<p>Совет 44. Используйте функции контейнеров вместо одноименных алгоритмов</p>
</title>
<p>Некоторые контейнеры содержат функции, имена которых совпадают с именами алгоритмов STL. Так, в ассоциативных контейнерах существуют функции count, find, lower_bound, upper_bound и equal_range, а в контейнере list предусмотрены функции remove, remove_if, unique, sort, merge и reverse. Как правило, эти функции используются вместо одноименных алгоритмов, что объясняется двумя причинами. Во-первых, функции классов быстрее работают. Во-вторых, они лучше интегрированы с контейнерами (особенно ассоциативными), чем алгоритмы. Дело в том, что алгоритмы и одноименные функции классов обычно работают <emphasis><strong>не совсем </strong></emphasis>одинаково.</p>
<p>Начнем с ассоциативных контейнеров. Допустим, имеется множество set<emphasis><strong>&lt;i</strong></emphasis>nt&gt;, содержащее миллион значений, и вы хотите найти позицию первого вхождения числа 727, если оно присутствует. Ниже приведены два очевидных способа поиска:</p>
<p><code>set&lt;int&gt; s;// Создать множество</code></p>
<p><code>// и занести в него </code></p>
<p><code>// миллион чисел</code></p>
<p><code>set&lt;int&gt;::iterator i = s.find(727);// Функция find контейнера</code></p>
<p><code>f(i!=s.end())...</code></p>
<p><code>set&lt;int&gt;::iterator i = find(s.begin(), s.end(), 727); // Алгоритм find </code></p>
<p><code>f(i!=s.end())...</code></p>
<p>Функция класса find работает с логарифмической сложностью, поэтому независимо от того, присутствует ли число 727 в множестве или нет, set:: find в процессе поиска выполнит не более 40 сравнений, а обычно потребуется не более 20. С другой стороны, алгоритм find работает с линейной сложностью, поэтому при отсутствии числа 727 будет выполнено 1 000 000 сравнений. Впрочем, даже если число 727 присутствует, алгоритм find в процессе поиска выполняет в среднем 500 000 сравнений. Результат явно не в пользу алгоритма find.</p>
<p>Кстати, я не совсем точно указал количество сравнений для функции find, поскольку оно зависит от реализации, используемой ассоциативными контейнерами. В большинстве реализаций используются красно-черные деревья — особая разновидность сбалансированных деревьев с разбалансировкой по степеням 2. В таких реализациях максимальное количество сравнений, необходимых для поиска среди миллиона значений, равно 38, но в подавляющем большинстве случаев требуется не более 22 сравнений. Реализация, основанная на идеально сбалансированных деревьях, никогда не требует более 21 сравнения, но на практике по общему быстродействию идеально сбалансированные деревья уступают «красно-черным». По этой причине в большинстве реализаций STL используются «красно-черные» деревья.</p>
<p>Различия между функцией класса и алгоритмом find не ограничиваются быстродействием. Как объясняется в совете 19, алгоритмы STL проверяют «одинаковость» двух объектов по критерию равенства, а ассоциативные контейнеры используют критерий эквивалентности. Таким образом, алгоритм find ищет 727 по критерию равенства, а функция find — по критерию эквивалентности. Различия в критериях иногда приводят к изменению результата поиска. Например, в совете 19 было показано, как применение алгоритма find для поиска информации в ассоциативном контейнере завершается неудачей, тогда как аналогичный поиск функцией find привел бы к успеху! При работе с ассоциативными контейнерами функциональные формы find, count и т. д. предпочтительнее алгоритмических, поскольку их поведение лучше согласуется с другими функциями этих контейнеров. Вследствие различий между равенством и эквивалентностью алгоритмы не столь последовательны.</p>
<p>Особенно ярко это различие проявляется при работе с контейнерами map и multimap, потому что эти контейнеры содержат объекты pair, но их функции учитывают только значение ключа каждой пары. По этой причине функция count считает только пары с совпадающими ключами (естественно, «совпадение» определяется по критерию эквивалентности); значение, ассоциированное с ключом, игнорируется. Функции find, lower_bound и т. д. поступают аналогично. Чтобы алгоритмы также ограничивались анализом ключа в каждой паре, вам придется выполнять акробатические трюки, описанные в совете 23 (что позволит заменить проверку равенства проверкой эквивалентности).</p>
<p>С другой стороны, если вы стремитесь к максимальной эффективности, то фокусы совета 23 в сочетании с логарифмической сложностью поиска алгоритмов из совета 34 могут показаться не такой уж высокой ценой за повышение быстродействия. А если вы <emphasis><strong>очень сильно </strong></emphasis>стремитесь к максимальной эффективности, подумайте об использовании нестандартных хэшированных контейнеров (см. совет 25), хотя в этом случае вы также столкнетесь с различиями между равенством и эквивалентностью.</p>
<p>Таким образом, для стандартных ассоциативных контейнеров применение функций вместо одноименных алгоритмов обладает сразу несколькими преимуществами. Во-первых, вы получаете логарифмическую сложность вместо линейной. Во-вторых, «одинаковость» двух величин проверяется по критерию эквивалентности, более естественному для ассоциативных контейнеров. В-третьих, при работе с контейнерами шар и multimap автоматически учитываются только значения ключей вместо полных пар (ключ, значение). Эти три фактора достаточно убедительно говорят в пользу функций классов.</p>
<p>Перейдем к функциям контейнера list, имена которых совпадают с именами алгоритмов STL. В этом случае эффективность является практически единственным фактором. Алгоритмы, у которых в контейнере list существуют специализированные версии (remove, remove_if, unique, sort, merge и reverse), копируют объекты, a list-версии ничего не копируют; они просто манипулируют указателями, соединяющими узлы списка. По алгоритмической сложности функции классов и алгоритмы одинаковы, но если предположить, что операции с указателями обходятся значительно дешевле копирования объектов, list-версии обладают лучшим быстродействием.</p>
<p>Следует помнить, что list-версии часто ведут себя иначе, чем их аналоги-алгоритмы. Как объясняется в совете 32, для фактического удаления элементов из контейнера вызовы алгоритмов remove, remove_if и unique должны сопровождаться вызовами erase, однако одноименные функции контейнера list честно уничтожают элементы, и последующие вызовы erase не нужны.</p>
<p>Принципиальное различие между алгоритмом sort и функцией sort контейнера list заключается в том, что алгоритм неприменим к контейнерам list, поскольку ему не могут передаваться двусторонние итераторы list. Алгоритм merge также отличается от функции merge контейнера list — алгоритму не разрешено модифицировать исходные интервалы, тогда как функция list:: merge <emphasis><strong>всегда </strong></emphasis>модифицирует списки, с которыми она работает.</p>
<p>Теперь вы располагаете всей необходимой информацией. Столкнувшись с выбором между алгоритмом STL и одноименной функцией контейнера, предпочтение следует отдавать функции контейнера. Она почти всегда эффективнее работает и лучше интегрируется с обычным поведением контейнеров.</p>
</section>
<section>
<title>
<p>Совет 45. Различайте алгоритмы count, find, binary_search, lower_bound, upper_bound и equal_range</p>
</title>
<p>Предположим, вы ищете некоторый объект в контейнере или в интервале, границы которого обозначены итераторами. Как это сделать? В вашем распоряжении целый арсенал алгоритмов: count, find, binary_search, lower_bound, upper_bound и equal_range. Как же принять верное решение?</p>
<p>Очень просто. Основными критериями должны быть скорость и простота.</p>
<p>Временно предположим, что границы интервала поиска обозначены итераторами. Случай с поиском во всем контейнере будет рассмотрен ниже.</p>
<p>При выборе стратегии поиска многое зависит от того, определяют ли итераторы сортированный интервал. Если это условие выполнено, воспользуйтесь алгоритмами binary_search, lower_bound, upper_bound и equal_range для проведения быстрого поиска (обычно с логарифмической сложностью — см. совет 34). Если интервал не отсортирован, выбор ограничивается линейными алгоритмами count, count_if, find и find_if. В дальнейшем описании _if-версии алгоритмов count и find игнорируются, как и разновидности binary_search, lower_bound, upper_bound и equal_range, которым при вызове передается предикат. Алгоритм поиска выбирается по одним и тем же соображениям независимо от того, используете ли вы стандартный предикат или задаете свой собственный.</p>
<p>Итак, в несортированных интервалах выбор ограничивается алгоритмами count и find. Эти алгоритмы решают несколько отличающиеся задачи, к которым следует присмотреться повнимательнее. Алгоритм count отвечает на вопрос: «Присутствует ли заданное значение, и если присутствует — то в каком количестве экземпляров?». Для алгоритма find вопрос звучит так: «Присутствует ли заданное значение, и если присутствует — то где именно?»</p>
<p>Допустим, вы просто хотите проверить, присутствует ли в списке некоторое значение w класса Widget. При использовании алгоритма count решение выглядит так:</p>
<p><code>list&lt;Widget&gt; lw;// Список объектов Widget</code></p>
<p><code>Widget w;// Искомое значение класса Widget</code></p>
<p><code>if (count(lw.begin().lw.end(),w)){</code></p>
<p><code>// Значение w присутствует в lw</code></p>
<p><code>} else {</code></p>
<p><code>// Значение не найдено</code></p>
<p><code>}</code></p>
<p>В приведенном фрагменте продемонстрирована стандартная идиома: применение count для проверки существования. Алгоритм count возвращает либо ноль, либо положительное число; в программе ненулевое значение интерпретируется как логическая истина, а ноль — как логическая ложь. Возможно, следующая конструкция более четко выражает суть происходящего:</p>
<p><code>if (count(lw.begin().lw.end(),w)!=0)...</code></p>
<p>Некоторые программисты предпочитают эту запись, но неявное преобразование, как в приведенном выше примере, встречается достаточно часто.</p>
<p>Решение с алгоритмом find выглядит чуть сложнее, поскольку возвращаемое значение приходится сравнивать с конечным итератором списка:</p>
<p><code>if(find(lw.begin(), lw.end(),w) !=w.end()){</code></p>
<p><code>...</code></p>
<p><code>} else {</code></p>
<p><code>...</code></p>
<p><code>}</code></p>
<p>В контексте проверки существования идиоматическое использование count чуть проще кодируется. С другой стороны, оно также менее эффективно при успешном поиске, поскольку find при обнаружении искомого значения немедленно прекращает поиск, a count продолжает искать дополнительные экземпляры до конца интервала. Для большинства программистов выигрыш в эффективности компенсирует дополнительные хлопоты, связанные с программированием find.</p>
<p>Впрочем, простой проверки существования во многих случаях бывает недостаточно; требуется также найти в интервале первый объект с заданным значением. Например, этот объект можно вывести, вставить перед ним другой объект или удалить его (удаление в процессе перебора рассматривается в совете 9). Если требуется узнать, какой объект (или объекты) имеют заданное значение, воспользуйтесь алгоритмом find:</p>
<p><code>list&lt;Widget&gt;::iterator i = find(lw.begin(),lw.end(),w);</code></p>
<p><code>if (i!=lw.end()){</code></p>
<p><code>	// Успешный поиск, i указывает на первый экземпляр</code></p>
<p><code>} else {</code></p>
<p><code>	// Значение не найдено</code></p>
<p><code>}</code></p>
<p>При работе с сортированными интервалами существуют и другие варианты, и им определенно стоит отдать предпочтение. Алгоритмы count и find работают с линейной сложностью, тогда как алгоритмы поиска в сортированных интервалах (binary_search, lower_bound, upper_bound и equal_range) обладают логарифмической сложностью.</p>
<p>Переход от несортированных интервалов к сортированным влечет за собой изменение критерия сравнения двух величин. Различия между критериями подробно описаны в совете 19, поэтому я не стану повторяться и замечу, что алгоритмы count и find используют критерий равенства, а алгоритмы binary_search, lower_bound, upper_bound и equal range основаны на критерии эквивалентности.</p>
<p>Присутствие величины в сортированном интервале проверяется алгоритмом binary_search. В отличие от функции bsearch из стандартной библиотеки С (а значит, и стандартной библиотеки С++), алгоритм binary_search возвращает только bool. Алгоритм отвечает на вопрос: «Присутствует ли заданное значение в интервале?», и возможны только два ответа: «да» и «нет». Если вам понадобится дополнительная информация, выберите другой алгоритм.</p>
<p>Пример применения binary_search к сортированному вектору (преимущества сортированных векторов описаны в совете 23):</p>
<p><code>vector&lt;Widget&gt; vw;</code></p>
<p><code>sort (vw. Begin(),vw.end());</code></p>
<p><code>// Создать вектор, заполнить // данными и отсортировать</code></p>
<p><code>Widget w:// Искомое значение</code></p>
<p><code>if(binary_search(vw.begin().vw.end(),w)) {</code></p>
<p><code>// Значение w присутствует в vw</code></p>
<p><code>} else {</code></p>
<p><code>// Значение не найдено</code></p>
<p><code>}</code></p>
<p>Если у вас имеется сортированный интервал и вы ищете ответ на вопрос: «Присутствует ли заданное значение в интервале, и если присутствует — то где именно?», следует использовать алгоритм equal_range, хотя на первый взгляд кажется, что вам нужен алгоритм lower_bound. Вскоре мы вернемся к equal_range, а пока проанализируем поиск в интервалах с применением алгоритма lower_bound.</p>
<p>При поиске заданной величины в интервале алгоритм lower_bound возвращает итератор, указывающий на первый экземпляр этой величины (в случае успешного поиска) или на правильную позицию вставки (в случае неудачи). Таким образом, алгоритм lower_bound отвечает на вопрос: «Присутствует ли заданное значение в интервале? Если присутствует, то где находится первый экземпляр, а если нет — где он должен находиться?». Как и в случае с алгоритмом find, результат lower_ bound необходимо дополнительно проверить и убедиться в том, что он указывает на искомое значение. Но в отличие от find, его нельзя просто сравнить с конечным итератором. Вместо этого приходится брать объект, идентифицированный алгоритмом lower_bound, и проверять, содержит ли он искомое значение.</p>
<p>Многие программисты используют lower_bound примерно так:</p>
<p><code>vector&lt;Widget&gt;::iterator =lower_bound(vw,begin().vw.end(),w):</code></p>
<p><code>if (i!=vw.end()&amp;&amp;*i=w){// Убедиться в том, что i указывает</code></p>
<p><code>// на объект, и этот объект имеет искомое </code></p>
<p><code>// значение. Ошибка!!!!</code></p>
<p><code>// Значение найдено, i указывает на первый </code></p>
<p><code>// экземпляр объекта с этим значением</code></p>
<p><code>} else {</code></p>
<p><code>// Значение не найдено</code></p>
<p><code>}</code></p>
<p>В большинстве случаев такая конструкция работает, но в действительности она содержит ошибку. Присмотритесь к проверяемому условию:</p>
<p><code>if (i!=vw.end()&amp;&amp;*i=w){</code></p>
<p>В этом условии проверяется <emphasis><strong>равенство, </strong></emphasis>тогда как lower_bound использует при поиске критерий <emphasis><strong>эквивалентности. </strong></emphasis>Как правило, результаты проверки по двум критериям совпадают, но, как показано в совете 19, это не всегда так. В таких ситуациях приведенный выше фрагмент не работает.</p>
<p>Чтобы исправить ошибку, необходимо убедиться в том, что итератор, полученный от lower__bound, указывает на объект со значением, эквивалентным искомому. Проверку можно выполнить вручную (в совете 19 показано, как это делается, а в совете 24 приведен пример ситуации, когда такое решение оправданно), однако сделать это непросто, поскольку при этом должна использоваться та же функция сравнения, как и при вызове lower_bound. В общем случае мы имеем дело с произвольной</p>
<p>функцией (или объектом функции). При передаче lower_bound функции сравнения эта же функция должна использоваться и в «ручной» проверке эквивалентности; следовательно, при изменении функции сравнения, передаваемой lower_rbound, вам придется внести соответствующие изменения в проверку эквивалентности. В принципе, синхронизировать функции сравнения не так уж сложно, но об этом необходимо помнить, а при программировании хлопот и без этого хватает.</p>
<p>Существует простое решение: воспользуйтесь алгоритмом equal_range. Алгоритм возвращает <emphasis><strong>пару </strong></emphasis>итераторов; первый совпадает с итератором, возвращаемым lower_bound, а второй совпадает с итератором, возвращаемым upper_bound (то есть указывает в позицию за интервалом значений, эквивалентных искомому). Таким образом, алгоритм equal_range возвращает пару итераторов, обозначающих интервал значений, эквивалентных искомому. Согласитесь, имя алгоритма выбрано довольно удачно. Возможно, правильнее было бы назвать его equvalent_range, но и equal _range воспринимается неплохо.</p>
<p>Относительно возвращаемого значения equal_range необходимо сделать два важных замечания. Если два итератора совпадают, это говорит о том, что интервал пуст, а значение не найдено. По этому факту можно судить о том, было ли найдено совпадение. Пример:</p>
<p><code>vector&lt;Widget&gt; vw;</code></p>
<p><code>sort (vw.begin(), v.end());</code></p>
<p><code>typedef vector&lt;Widget&gt;::iterator VWIter; // Вспомогательные</code></p>
<p><code>typedef pair&lt;VWIter,VWIter&gt; VWIterPair: // определения типов</code></p>
<p><code>VWIterPar p = equal_range(vw.begin(),vw.end(),w);</code></p>
<p><code>if (p.first != p.second){// Если equal_range возвращает</code></p>
<p><code>	// непустой интервал...</code></p>
<p><code>	// Значение найдено, p.first </code></p>
<p><code>	// указывает на первый элемент </code></p>
<p><code>	// интервала, а p.second -</code></p>
<p><code>	// на позицию за последним элементом</code></p>
<p><code>} else {</code></p>
<p><code>	// Значение не найдено, p.first </code></p>
<p><code>	// и p.second указывают на точку </code></p>
<p><code>	// вставки искомого значения</code></p>
<p><code>}</code></p>
<p>В этом фрагменте используется только критерий эквивалентности, поэтому он всегда верен.</p>
<p>Другая особенность возвращаемого значения equal_range заключается в том, что расстояние между двумя итераторами равно количеству объектов в интервале, то есть количеству объектов, эквивалентных искомому объекту. В результате equal_range не только выполняет функции find для сортированных интервалов, но и заменяет count. Например, поиск в vw объектов Widget, эквивалентных w, с последующим выводом их количества может выполняться следующим образом:</p>
<p><code>VWIterPair р = equal_range(vw.begin(),vw.end(),w);</code></p>
<p><code>cout « "There are " « distance(p.first,p.second)</code></p>
<p><code>« " elements in vw equivalent to w.";</code></p>
<p>До настоящего момента предполагалось, что в интервале ищется некоторое значение, но есть ситуации, в которых возникает задача поиска <emphasis><strong>граничной позиции. </strong></emphasis>Предположим, у нас имеется класс Timestamp и вектор объектов Timestamp, отсортированный от «старых» объектов к «новым»:</p>
<p><code>class Timestamp {...};</code></p>
<p><code>bool operator&lt;(const Timestamp&amp; lhs. //Проверяет, предшествует ли</code></p>
<p><code>		const Timestamp&amp; rhs); // объект lhs объекту rhs по времени</code></p>
<p><code>vector&lt;Timestamp&gt; vt;// Создать вектор, заполнить данными</code></p>
<p><code>		// и отсортировать так, чтобы </code></p>
<p><code>sort(vt.begin(),vt.end()); // "старые" объекты предшествовали "новым"</code></p>
<p>Предположим, из vt требуется удалить все объекты, предшествующие некоторому пороговому объекту ageLimit. В данном случае не нужно искать в vt объект Timestamp, эквивалентный ageLimit, поскольку объекта с точно совпадающим значением может и не быть. Вместо этого в vt ищется граничная позиция, то есть первый элемент, не старший ageLimit. Задача решается элементарно, поскольку алгоритм lowebound предоставляет всю необходимую информацию:</p>
<p><code>Timestamp ageLimit;</code></p>
<p><code>vt.erase(vt.begin().lower_bound(vt.begin(),// Удалить из vt все объекты,</code></p>
<p><code>	vt.end(),// предшествующие значению</code></p>
<p><code>	ageLimit));// ageLimit</code></p>
<p>Слегка изменим условия задачи. Допустим, требуется удалить все объекты, предшествующие или равные ageLmt. Для этого необходимо найти первый объект <emphasis><strong>после </strong></emphasis>ageLmt. Для решения задачи идеально подходит алгоритм upper_bound:</p>
<p><code>vt.erase(vt.begin(),upper_bound(vt.begin(). // Удалить из vt все объекты,</code></p>
<p><code>		vt.end(), // предшествующие или</code></p>
<p><code>		ageLimit)); </code></p>
<p><code>// эквивалентные ageLimit</code></p>
<p>Алгоритм upper_bound также часто применяется при вставке в сортированные интервалы, когда объекты с эквивалентными значениями должны следовать в контейнере в порядке вставки. Рассмотрим сортированный список объектов Person, в котором объекты сортируются по имени:</p>
<p><code>class Person { public:</code></p>
<p><code>const string&amp; name() const;</code></p>
<p><code>…</code></p>
<p><code> }</code></p>
<p><code>struct PersonNameLess:</code></p>
<p><code>	public binary_function&lt;Person, Person, bool&gt; { // См. совет 40</code></p>
<p><code>	bool operator()(const Person&amp; lhs, const Person&amp; rhs) const </code></p>
<p><code>{</code></p>
<p><code>	return lhs.name()&lt;rhs.name();</code></p>
<p><code>}</code></p>
<p><code>list&lt;Person&gt; lp;</code></p>
<p><code>lp.sort(PersonNameLess());// Отсортировать lp по критерию</code></p>
<p><code>// PersonNameLess</code></p>
<p>Чтобы список сортировался требуемым образом (по имени, с хранением эквивалентных имен в порядке вставки), можно воспользоваться алгоритмом upper_ bound для определения позиции вставки:</p>
<p><code>Person newPerson;</code></p>
<p><code>lp.insert(upper_bound(lp.begin(),// Вставить newPerson за последним</code></p>
<p><code>	Ip.end(),	// объектом lр. предшествующим</code></p>
<p><code>	newPerson,	// или эквивалентным newPerson</code></p>
<p><code>	PersonNameLess()).</code></p>
<p><code>newPerson);</code></p>
<p>Приведенное решение работоспособно и достаточно удобно, но не стройте иллюзий насчет того, что оно каким-то волшебным способом обеспечивает поиск точки вставки в контейнер list с логарифмической сложностью. Как объясняется в совете 34, при работе с list поиск занимает линейное время, но при этом выполняется логарифмическое количество сравнений.</p>
<p>До настоящего момента рассматривался только случай, когда поиск осуществляется в интервале, определяемом парой итераторов. Довольно часто работать приходится со всем контейнером вместо интервала. В этом случае необходимо различать последовательные и ассоциативные контейнеры. Для стандартных последовательных контейнеров (vector, string, deque и list) достаточно следовать рекомендациям, изложенным ранее, используя начальный и конечный итераторы контейнера для определения интервала.</p>
<p>Со стандартными ассоциативными контейнерами (set, multiset, map, multimap) дело обстоит иначе. В них предусмотрены функции поиска, которые по своим возможностям обычно превосходят алгоритмы STL Превосходство функций контейнеров перед алгоритмами подробно рассматривается в совете 44; если говорить кратко — они быстрее работают и ведут себя более последовательно. К счастью, имена функций обычно совпадают с именами соответствующих алгоритмов, поэтому там, где речь идет об алгоритмах count, find, lower_bound, upper_bound и equal_range, при поиске в ассоциативных контейнерах вместо них достаточно выбрать одноименную функцию. К сожалению, для алгоритма binary_search парной функции не существует. Чтобы проверить наличие значения в контейнере set или map, воспользуйтесь идиоматической ролью count как условия проверки:</p>
<p><code>set&lt;Widget&gt; s;// Создать множество, заполнить данными</code></p>
<p><code>Widget w:// Искомое значение</code></p>
<p><code>if (s.count(w)) { // Существует значение, эквивалентное w</code></p>
<p><code> } else {</code></p>
<p><code>// Эквивалентное значение не существует</code></p>
<p><code>}</code></p>
<p>При проверке присутствия значений в контейнерах multiset или multimap функция find обычно превосходит count, поскольку она останавливается при обнаружении первого объекта с искомым значением, а функция count в худшем случае просматривает все элементы контейнера.</p>
<p>Тем не менее при подсчете объектов в ассоциативных контейнерах count надежно занимает свою нишу. В частности, вызов count предпочтительнее вызова equal_range с последующим применением distance к полученным итераторам. Во-первых, само название функции подсказывает ее смысл — слово count означает «подсчет». Во-вторых, count упрощает работу программиста, поскольку ему не приходится создавать пару и передавать ее компоненты при вызове distance. В-третьих, count работает чуть быстрее.</p>
<p>Попробуем подвести итог всему, о чем говорилось в настоящем совете. Информация собрана в следующей таблице.</p>
<table>
<tr>
<td/>
<td align="left">Алгоритм</td>
<td align="left">Функция контейнера</td>
</tr>
<tr>
<td align="left">Что вы хотите узнать</td>
<td align="left">Несортированный интервал</td>
<td align="left">Сортированный интервал</td>
<td align="left">Для set и map</td>
<td align="left">Для multiset и multimap</td>
</tr>
<tr>
<td align="left">Присутствует ли заданное значение?</td>
<td align="left">find</td>
<td align="left">binary_search</td>
<td align="left">count</td>
<td align="left">find</td>
</tr>
<tr>
<td align="left">Присутствует ли заданное значение? И если присутствует, то где находится первый объект с этим значением?</td>
<td align="left">find</td>
<td align="left">equal_range</td>
<td align="left">find</td>
<td align="left">find или lower_bound (см. ранее)</td>
</tr>
<tr>
<td align="left">Где находится первый объект со значением, не предшествующим заданному?</td>
<td align="left">find_if</td>
<td align="left">lower_bound</td>
<td align="left">lower_bound</td>
<td align="left">lower_bound</td>
</tr>
<tr>
<td align="left">Где находится первый объект со значением, следующим после заданного?</td>
<td align="left">find_if</td>
<td align="left">upper_bound</td>
<td align="left">upper_bound</td>
<td align="left">upper_bound</td>
</tr>
<tr>
<td align="left">Сколько объектов имеют</td>
<td align="left">count</td>
<td align="left">equal_range</td>
<td align="left">count</td>
<td align="left">count</td>
</tr>
<tr>
<td align="left">заданное значение?</td>
<td/>
<td/>
<td/>
<td/>
</tr>
<tr>
<td align="left">Где находятся все</td>
<td align="left">equal_range</td>
<td align="left">equal_range</td>
<td align="left">equal_</td>
<td align="left">Find (итеративный вызов)</td>
</tr>
<tr>
<td align="left">объекты с заданным</td>
<td/>
<td/>
<td align="left">range</td>
<td/>
</tr>
<tr>
<td align="left">значением?</td>
<td/>
<td/>
<td/>
<td/>
</tr>
</table>
<p>Несколько странно выгладит частое присутствие equal_range в столбце, относящемся к сортированным интервалам. Оно связано с особой ролью проверки эквивалентности при поиске. Использование lower_bound и upper_bound чревато ошибочной проверкой равенства, а при использовании equal_range более естественно выглядит проверка эквивалентности. Во второй строке предпочтение отдается equal_range еще по одной причине: equal_range работает с логарифмическим временем, а вызов find связан с линейными затратами времени.</p>
<p>Для контейнеров multiset и multimap в качестве возможных кандидатов для поиска первого объекта с заданным значением указаны два алгоритма, find и lowe<emphasis><strong>r_ </strong></emphasis>bound. Обычно для решения этой задачи выбирается find — возможно, вы обратили внимание, что именно этот алгоритм указан в таблице для контейнеров set и map. Однако multi -контейнеры не гарантируют, что при наличии нескольких элементов с заданным значением find найдет <emphasis><strong>первый </strong></emphasis>элемент в контейнере; известно лишь то, что будет найден <emphasis><strong>один </strong></emphasis>из этих элементов. Если вы действительно хотите найти первый объект с заданным значением, воспользуйтесь lower_bound и выполните вручную вторую часть проверки эквивалентности, описанной в совете 19 (без этой проверки можно обойтись при помощи equal _range, но вызов equal range обходится дороже, чем вызов lower_bound).</p>
<p>Выбрать между count, find, binary_search, lower_bound, upper_bound и equal_range несложно. Предпочтение отдается тому алгоритму или функции, которые обладают нужными возможностями, обеспечивают нужное быстродействие и требуют минимальных усилий при вызове. Следуйте этой рекомендации (или обращайтесь к таблице), и у вас никогда не будет проблем с выбором.</p>
</section>
<section>
<title>
<p>Совет 46. Передавайте алгоритмам объекты функций вместо функций</p>
</title>
<p>Часто говорят, что повышение уровня абстракции языков высокого уровня приводит к снижению эффективности сгенерированного кода. Александр Степанов, изобретатель STL, однажды разработал небольшой комплекс тестов для оценки «платы за абстракцию» при переходе с С на С++. В частности, результаты этих тестов показали, что код, сгенерированный для работы с классом, содержащим double, почти всегда уступает по эффективности соответствующему коду, непосредственно работающему с double. С учетом сказанного вас может удивить тот факт, что передача алгоритмам объектов функций STL — то есть объектов, маскирующихся под функции, — обычно обеспечивает <emphasis><strong>более </strong></emphasis>эффективный код, чем передача «настоящих» функций.</p>
<p>Предположим, вы хотите отсортировать вектор чисел типа double по убыванию. Простейшее решение этой задачи средствами STL основано на использовании алгоритма sort с объектом функции типа greater&lt;double&gt;:</p>
<p><code>vector&lt;double&gt; v;</code></p>
<p><code>sort(v.begin().v.end(),greater&lt;double&gt;());</code></p>
<p>Вспомнив о «плате за абстракцию», программист решает заменить объект функции «настоящей» функцией, которая к тому же оформлена как подставляемая (inline):</p>
<p><code>inline</code></p>
<p><code>bool doubleGreater(double d1, double d2) {</code></p>
<p><code>	return d1&gt;d2;</code></p>
<p><code>}</code></p>
<p><code>sort(v.begin(),v.end(),doubleGreater);</code></p>
<p>Как ни странно, хронометраж двух вызовов sort показывает, что вызов с greater-&lt;double&gt; почти всегда работает быстрее. В своих тестах я сортировал вектор, содержащий миллион чисел типа double, на четырех разных платформах STL с оптимизацией по скорости, и версия с greater&lt;double&gt; всегда работала быстрее. В худшем случае выигрыш в скорости составил 50%, в лучшем он достигал 160%. Вот тебе и «плата за абстракцию»...</p>
<p>Факт объясняется просто. Если функция operator() объекта функции была объявлена подставляемой (явно, с ключевым словом inline, или косвенно, посредством определения внутри определения класса), большинство компиляторов благополучно подставляет эту функцию во время создания экземпляра шаблона при вызове алгоритма. В приведенном выше примере это происходит с функцией greater&lt;double&gt;::operator(). В результате код sort не содержит ни одного вызова функций, а для такого кода компилятор может выполнить оптимизацию, недоступную при наличии вызовов (связь между подстановкой функций и оптимизацией компиляторов рассматривается в совете 33 «Effective С++» и главах 8-10 книги  «Efficient С++» [10]).</p>
<p>При вызове sort с передачей doubleGreater ситуация выглядит иначе. Чтобы убедиться в этом, необходимо вспомнить, что передача функции в качестве параметра другой функции невозможна. При попытке передачи функции в качестве параметра компилятор автоматически преобразует функцию в <emphasis><strong>указатель </strong></emphasis>на эту функцию, поэтому при вызове передается указатель. Таким образом, при вызове</p>
<p><code>sort(v.begin(),v.end(),doubleGreater);</code></p>
<p>алгоритму sort передается не doubleGreater<emphasis><strong>, </strong></emphasis>а указатель на doubleGreater<emphasis><strong>. </strong></emphasis>При создании экземпляра шаблона объявление сгенерированной функции выглядит так:</p>
<p><code>void sort(vector&lt;double&gt;::iterator first, // Начало интервала</code></p>
<p><code>vector&lt;double&gt;:iterator last, // Конец интервала</code></p>
<p><code>bool (*comp)(double,double)); // Функция сравнения</code></p>
<p>Поскольку comp является указателем на функцию, при каждом его использовании внутри sort происходит косвенный вызов функции (то есть вызов через указатель). Большинство компиляторов не пытается подставлять вызовы функций, вызываемых через указатели, даже если функция объявлена с ключевым словом inline и оптимизация выглядит очевидной. Почему? Наверное, потому, что разработчики компиляторов не считают нужным ее реализовать. Пожалейте их — народ постоянно чего-нибудь требует, а успеть все невозможно. Впрочем, это вовсе не означает, что требовать не нужно.</p>
<p>Подавление подстановки кода функций объясняет один факт, который кажется невероятным многим опытным программистам С: функция С++ sort почти всегда превосходит по скорости функцию С qsort. Конечно, в С++ приходится создавать экземпляры шаблонов функций и вызывать operator(), тогда как в С все ограничивается простым вызовом функции, однако все «излишества» С++ теряются во время компиляции. На стадии выполнения sort обращается к подставленной функции сравнения (при условии, что функция была объявлена с ключевым словом inline, а ее тело доступно на стадии компиляции), тогда как qsort вызывает функцию сравнения через указатель. Результат — sort работает гораздо быстрее. В моих тестах с вектором, содержащим миллион чисел double, превосходство по скорости достигало 670%, но я не призываю верить мне на слово. Вы легко убедитесь в том, что при передаче объектов функций в качестве параметров алгоритмов «плата за абстракцию» превращается в «премию за абстракцию».</p>
<p>Существует и другая причина для передачи объектов функций в параметрах алгоритмов, не имеющая ничего общего с эффективностью. Речь идет о компилируемости программ. По каким-то загадочным причинам некоторые платформы STL отвергают абсолютно нормальный код — это связано с недоработками то ли компилятора, то ли библиотеки, то ли и того и другого. Например, одна распространенная платформа STL отвергает следующий (вполне допустимый) фрагмент, выводящий в cout длину всех строк в множестве:</p>
<p><code>set&lt;string&gt; s;</code></p>
<p><code>transform(s.begin(), s.end(),</code></p>
<p><code>	ostream_iterator&lt;string::size_type&gt;(cout."\n"),</code></p>
<p><code>	mem_fun_ref(&amp;string::size)</code></p>
<p><code>);</code></p>
<p>Проблема возникает из-за ошибки в работе с константными функциями классов (такими как string::size) в этой конкретной платформе STL. Обходное решение заключается в использовании объекта функции:</p>
<p><code>struct StringSize:</code></p>
<p><code>public_unary_function&lt;string,string::size_type&gt; { // См. совет 40</code></p>
<p><code>   string::size_type operator() (const string&amp; s) const</code></p>
<p><code>{</code></p>
<p><code>	return s.size();</code></p>
<p><code>}</code></p>
<p><code>transform (s.begin(),s.end(),</code></p>
<p><code>	Ostream_iterator&lt;string::size_type&gt;(cout,"\n"),</code></p>
<p><code>	StringSize();</code></p>
<p>Существуют и другие обходные решения, но приведенный фрагмент хорош не только тем, что он компилируется на всех известных мне платформах STL. Он также делает возможной подстановку вызова string::size, что почти наверняка невозможно в предыдущем фрагменте с передачей <code>mem_fun_ref(&amp;string:: size)</code>. Иначе говоря, определение класса функтора StringSize не только обходит недоработки компилятора, но и может улучшить быстродействие программы.</p>
<p>Другая причина, по которой объекты функций предпочтительнее обычных функций, заключается в том, что они помогают обойти хитрые синтаксические ловушки. Иногда исходный текст, выглядящий вполне разумно, отвергается компилятором по законным, хотя и неочевидным причинам. Например, в некоторых ситуациях имя экземпляра, созданного на базе шаблона функции, не эквивалентно имени функции. Пример:</p>
<p><code>template&lt;typename FPType&gt;	//Вычисление среднего</code></p>
<p><code>FPType average(FPType val1,FPType val2) //арифметического двух</code></p>
<p><code>{		//вещественных чисел </code></p>
<p><code>	return (vail + val2)/2;</code></p>
<p><code>};</code></p>
<empty-line/>
<p><code>template&lt;typename InputIter1. typename InputIter2&gt;</code></p>
<p><code>void wrteAverages(InputIter begin1,	//Вычислить попарные</code></p>
<p><code>InputIter end1,		//средние значения</code></p>
<p><code>InputIter begin2,	//двух серий элементов</code></p>
<p><code>ostream&amp; s)	//в потоке</code></p>
<p><code>{</code></p>
<p><code>transform(</code></p>
<p><code>	begin1,end1,begin2,</code></p>
<p><code>ostream_iterator&lt;typename iterator_traits&lt;InputIter1&gt;::value_type&gt;(s,"\n"),</code></p>
<p><code> average&lt;typename iterator traits&lt;lnputIter1&gt;::value_type&gt; // Ошибка?</code></p>
<p><code>};</code></p>
<p><code>};</code></p>
<p>Многие компиляторы принимают этот код, но по Стандарту С++ он считается недопустимым. Дело в том, что теоретически может существовать другой шаблон функции с именем average, вызываемый с одним параметром-типом. В этом случае выражение <code>average&lt;typename iterator_traits&lt;InputIter1&gt;:: value_type&gt;</code> становится неоднозначным, поскольку непонятно, какой шаблон в нем упоминается. В конкретном примере неоднозначность отсутствует, но некоторые компиляторы на вполне законном основании все равно отвергают этот код. Решение основано на использовании объекта функции:</p>
<p><code>template&lt;typename FPType&gt;</code></p>
<p><code>struct Average:</code></p>
<p><code>public binary_function&lt;FPType,FPType,FPType&gt;{ // См. совет 40</code></p>
<p><code>FPType operator()(FPType val1, FPType val2) const</code></p>
<p><code>	{</code></p>
<p><code>	   return average(val1,val2);</code></p>
<p><code>	}</code></p>
<p><code>};</code></p>
<p><code>template&lt;typename InputIter, typename InputIter2&gt;</code></p>
<p><code>void writeAverages(InputIter1 begin1, InputIter1 end1,</code></p>
<p><code>	InputIter2 begin2, ostream&amp; s)</code></p>
<p><code>{</code></p>
<p><code>	transform( begin1,end1,begin2,</code></p>
<p><code>	ostream_iterator&lt;typename iterator_traits&lt;InputIter1&gt;::value_type&gt;(s."\n"),</code></p>
<p><code>Average&lt;typename iterator_traits&lt;InputIter1&gt;::value_type()</code></p>
<p><code>);</code></p>
<p><code>}</code></p>
<p>Новая версия должна приниматься любым компилятором. Более того, вызовы Average::operator() внутри transform допускают подстановку кода, что не относится к экземплярам приведенного выше шаблона average, поскольку average является шаблоном функции, а не <emphasis><strong>объекта </strong></emphasis>функции.</p>
<p>Таким образом, преимущество объектов функций в роли параметров алгоритмов не сводится к простому повышению эффективности. Объекты функций также обладают большей надежностью при компиляции кода. Бесспорно, «настоящие» функции очень важны, но в области эффективного программирования в STL объекты функций часто оказываются полезнее.</p>
</section>
<section>
<title>
<p>Совет 47. Избегайте «нечитаемого» кода</p>
</title>
<p>Допустим, имеется вектор vector&lt;int&gt;. Из этого вектора требуется удалить все элементы, значение которых меньше <emphasis><strong>х, </strong></emphasis>но оставить элементы, предшествующие последнему вхождению значения, не меньшего <emphasis><strong>у. </strong></emphasis>В голову мгновенно приходит следующее решение:</p>
<p><code>vector&lt;int&gt; v; int х,у;</code></p>
<p><code>v.erase(</code></p>
<p><code>	remove_if(find_if(v.rbegin(),v.rend(),</code></p>
<p><code>		bind2nd(greater_equal&lt;int&gt;().y)).base(),</code></p>
<p><code>	v.end(),</code></p>
<p><code>	bind2nd(less&lt;int&gt;(),x)),</code></p>
<p><code>v.end());</code></p>
<p>Всего одна команда, и задача решена. Все просто и прямолинейно. Никаких проблем. Правда?</p>
<p>Не будем торопиться с выводами. Считаете ли вы приведенный код логичным и удобным в сопровождении? «Нет!» — воскликнет большинство программистов С++ с ужасом и отвращением. «Да!» — скажут считанные единицы с явным удовольствием. В этом и заключается проблема. То, что один программист считает выразительной простотой, другому представляется адским наваждением.</p>
<p>Насколько я понимаю, приведенный выше фрагмент вызывает беспокойство по двум причинам. Во-первых, он содержит слишком много вызовов функций. Чтобы понять, о чем идет речь, приведу ту же команду, в которой имена функций заменены обозначениями <emphasis>fn:</emphasis></p>
<p><code>V.f1(f2(f3(v.f40.v.f50.f6(f70.y)).f8().v.f90.f6(fl00,x)).v.f90);</code></p>
<p>Такая запись выглядит неестественно усложненной, поскольку из нее убраны отступы, присутствующие в исходном примере. Можно уверенно сказать, что большинство программистов С++ сочтет, что двенадцать вызовов десяти разных функций в одной команде — это перебор. Но программисты с опытом работы на функциональных языках типа Scheme могут считать иначе. По своему опыту могу сказать, что почти все программисты, которые просматривали этот фрагмент без малейших признаков удивления, имели основательный опыт программирования на функциональных языках. У большинства программистов С++ такой опыт отсутствует, так что если ваши коллеги не привыкли к многоуровневым вложениям вызовов функций, конструкции вроде предыдущего вызова erase будут приводить их в замешательство.</p>
<p>Второй недостаток приведенного кода заключается в том, что для его понимания нужно хорошо знать STL. В нем встречаются менее распространенные <strong>_if</strong>-формы алгоритмов find и remove, обратные итераторы (см. совет 26), преобразования reverse_iterator в iterator (см. совет 28), bind2nd и анонимные объекты функций, а также идиома erase-remove (см. совет 32). Опытный программист STL разберется в этой комбинации без особого труда, но гораздо больше будет таких, кто надолго задумается над ней. Если ваши коллеги далеко продвинулись в изучении STL, присутствие erase, remove_if, find_if, base и bind2nd в одной команде вполне допустимо, но если вы хотите, чтобы ваша программа была понятна программисту С++ со средним уровнем подготовки, я рекомендую разделить эту команду на несколько фрагментов.</p>
<p>Ниже приведен один из возможных вариантов (комментарии приводятся не только для книги — я бы включил их и в программу).</p>
<p><code>typedef vector&lt;int&gt;::iterator VecInter;</code></p>
<p><code>// Инициализировать angeBegin первым элементом v, большим или равным </code></p>
<p><code>// последнему вхождению у. Если такой элемент не существует,  rangeBegin </code></p>
<p><code>// инициируется значением v.begin() </code></p>
<p><code>VeclntIte<emphasis>r r</emphasis>angeBegin = find_if(v.rbegin().v.rend(),</code></p>
<p><code>		bind2nd(greater_equal&lt;int&gt;(),y)).base();</code></p>
<p><code>// Удалить от rangeBegin до v.end все элементы со значением, меньшим х </code></p>
<p><code>v.erase(remove_if(rangeBegin.v.end().bind2nd(less&lt;int&gt;().x)),v.end());</code></p>
<p>Возможно, даже этот вариант кое-кого смутит, поскольку он требует понимания идиомы erase-remove, но при наличии комментариев в программе и хорошего справочника по STL (например, «The С++ Standard Library» [3] или web-сайта SGI [21]) каждый программист С++ без особых усилий разберется, что же происходит в программе.</p>
<p>Обратите внимание: в процессе модификации я не отказался от использования алгоритмов и не попытался заменить их циклами. В совете 43 объясняется, почему алгоритмы обычно предпочтительнее циклов, и приведенные аргументы действуют и в этом случае. Основная цель при программировании заключается в создании кода, понятного как для компилятора, так и для читателя-человека, и обладающего приемлемым быстродействием. Алгоритмы почти всегда лучше подходят для достижения этой цели. Тем не менее, совет 43 также объясняет, почему интенсивное использование алгоритмов естественным образом приводит к частому вложению вызовов функций и использованию адаптеров функторов. Вернемся к постановке задачи, с которой начинается настоящий совет.</p>
<p>Допустим, имеется вектор vector&lt;int&gt;. Из этого вектора требуется удалить все элементы, значение которых меньше х, но оставить элементы, предшествующие последнему вхождению значения, не меньшего у.</p>
<p>Нетрудно придти к общей схеме решения:</p>
<p>•поиск последнего вхождения значения в векторе требует применения find или find_if с обратными итераторами;</p>
<p>•удаление элементов требует erase или идиомы erase-remove.</p>
<p>Объединяя эти две идеи, мы получаем следующий псевдокод, где «нечто» обозначает код, который еще не был наполнен смысловым содержанием:</p>
<p><code>v.erase(remove_if(find_if(v.rbegin(). v.rend(). <emphasis>нечто).</emphasis>base(). v.end(). <emphasis>нечто)).</emphasis></code></p>
<p><code>v.end());</code></p>
<p>При наличии такой схемы рассчитать, что же кроется за «нечто», совсем несложно. Вы не успеете опомниться, как придете к решению из исходного примера. Во время написания программы подобные решения выглядят вполне логичными, поскольку в них отражается последовательное применение базовых принципов (например, идиомы erase-remove плюс использование find с обратными итераторами). К сожалению, читателю вашей программы будет очень трудно разобрать готовый продукт на те идеи, из которых он был собран. «Нечитаемый» код легко пишется, но разобраться в нем трудно.</p>
<p>Впрочем, «нечитаемость» зависит от того, кто именно читает программу. Как упоминалось выше, некоторые программисты С++ вполне нормально относятся к конструкциям вроде приведенной в начале этого совета. Если такая картина типична для среды, в которой вы работаете, и вы ожидаете, что она останется таковой в будущем, не сдерживайте свои творческие порывы. Но если ваши коллеги недостаточно уверенно владеют функциональным стилем программирования и не столь хорошо разбираются в STL, умерьте свои амбиции и напишите что-нибудь вроде альтернативного решения, приведенного выше.</p>
<p>Банальный факт из области программирования: код чаще читается, чем пишется. Хорошо известно также, что на сопровождение программы уходит значительно больше времени, чем на ее разработку. Если программу нельзя прочитать и понять, ее нельзя и успешно сопровождать, а такие программы вообще никому не нужны. Чем больше вы работаете с STL, тем увереннее себя чувствуете и тем сильнее хочется использовать вложенные вызовы функций и создавать объекты функций «на лету». В этом нет ничего плохого, но всегда следует помнить, что написанную сегодня программу завтра придется кому-то читать — может быть, даже вам. Приготовьтесь к этому дню.</p>
<p>Да, используйте STL в своей работе. Используйте хорошо и эффективно... но избегайте написания «нечитаемого» кода. В долгосрочной перспективе такой код будет каким угодно, но только не эффективным.</p>
</section>
<section>
<title>
<p>Совет 48. Всегда включайте нужные заголовки</p>
</title>
<p>При программировании в STL нередко встречаются программы, которые успешно компилируются на одной платформе, но требуют дополнительных директив #include на другой. Этот раздражающий факт связан с тем, что Стандарт С++ (в отличие от Стандарта С) не указывает, какие стандартные заголовки могут или должны включаться в другие стандартные заголовки. Авторы реализаций пользуются предоставленной свободой и выбирают разные пути.</p>
<p>Попробую пояснить, что это значит на практике. Однажды я засел за пять платформ STL (назовем их А, В, С, D и Е) и попробовал экспериментальным путем определить, какие стандартные заголовки можно убрать, чтобы программа при этом нормально компилировалась. По этим данным становится ясно, какие заголовки включают другие заголовки директивой <emphasis><strong>#include</strong></emphasis>. Вот что я узнал:</p>
<p>•на платформах А и С &lt;vector&gt; включает &lt;string&gt;;</p>
<p>•на платформе С &lt;algorithm&gt; включает &lt;string&gt;;</p>
<p>•на платформах С и D &lt;iostream&gt; включает &lt;iterator&gt;;</p>
<p>•на платформе D &lt;iostream&gt; включает &lt;string&gt; и &lt;vector&gt;;</p>
<p>•на платформах D и Е &lt;string&gt; включает &lt;algorithm&gt;;</p>
<p>•во всех пяти реализациях &lt;set&gt; включает &lt;functional&gt;</p>
<p>За исключением последнего случая мне так и не удалось провести программу с убранным заголовком мимо реализации В. По закону Мэрфи вам всегда придется вести разработку на таких платформах, как А, С, D и Е, и переносить программы на такие платформы, как В, особенно когда это очень важная работа, которую необходимо сделать как можно скорее. Так бывает всегда.</p>
<p>Но не стоит осуждать компиляторы или разработчиков библиотек за трудности с переносом. Пропущенные заголовки на вашей ответственности. При каждой ссылке на элементы пространства имен std вы также отвечаете за включение соответствующих заголовков. Если заголовки опущены, программа теоретически может откомпилироваться, но другие платформы STL имеют полное право отвергнуть ваш код.</p>
<p>Чтобы вам было проще запомнить необходимые заголовки, далее приведена краткая сводка содержимого всех стандартных заголовков, относящихся к STL.</p>
<p>•Почти все контейнеры объявляются в одноименных заголовках, то есть <code>vector</code> объявляется в заголовке <code>&lt;vector&gt;</code><emphasis><strong>,  </strong></emphasis>list объявляется в заголовке &lt;list&gt; и т. д. Исключениями являются &lt;set&gt; и &lt;map&gt;. В заголовке &lt;set&gt; объявляются контейнеры set и multiset, а в заголовке &lt;map&gt; объявляются контейнеры map и multimap.</p>
<p>•Все алгоритмы, за исключением четырех, объявляются в заголовке &lt;algorithm&gt;. Исключениями являются алгоритмы accumulate (см. совет37), inner_poduct, adjacent_difference и partial_sum. Эти алгоритмы объявляются в заголовке &lt;numeric&gt;.</p>
<p>•Специализированные разновидности итераторов, включая istream_iterator и streambuf_iterator (см. совет 29), объявляются в заголовке &lt;iterator&gt;.</p>
<p>•Стандартные функторы (например less&lt;T&gt;) и адаптеры функторов (например not1 и bnd2nd) объявляются в заголовке &lt;functional&gt;.</p>
<p>Не забывайте включать соответствующую директиву <emphasis><strong>#include</strong></emphasis> при использовании любых из перечисленных компонентов, даже если платформа разработки позволяет обойтись и без нее. Ваше прилежание непременно окупится при переносе программы на другую платформу.</p>
</section>
<section>
<title>
<p>Совет 49. Научитесь читать сообщения компилятора</p>
</title>
<p>При определении вектора в программе вы имеете полное право указать конкретный размер:</p>
<p><code>vector&lt;int&gt; v(10); // Создать вектор из 10 элементов</code></p>
<p>Объекты string имеют много общего с vecto<emphasis><strong>r, </strong></emphasis>поэтому кажется, что следующая команда тоже допустима:</p>
<p><code>string s(10);// Попытаться определить string из 10 элементов</code></p>
<p>Однако эта команда не компилируется, поскольку у контейнера string не существует конструктора, вызываемого с аргументом типа int. На одной из платформ STL компилятор реагирует на эту команду следующим образом:</p>
<p><code>example.cpp(20):error С2664:'))thiscall std::basic_string&lt;char.struct std::char_ traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt;::std::basic_string&lt;char.struct std::char_ traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt;(const class std::allocator&lt;char&gt;&amp;)':cannot convert parameter 1 from 'const int' to 'const class std::allocator&lt;char&gt;&amp;' Reason: cannot convert from 'const int' to 'const class std::allocator&lt;char&gt;' No constructor could take the source type, or constructor overload resolution was ambiguous</code></p>
<p>Ну как, впечатляет? Первая часть сообщения выглядит как беспорядочное нагромождение символов, вторая часть ссылается на распределитель памяти, ни разу не упоминавшийся в исходном тексте, а в третьей части что-то говорится о вызове конструктора. Конечно, третья часть содержит вполне точную информацию, но для начала разберемся с первой частью, типичной для диагностики, часто встречающейся при работе со string.</p>
<p>Вспомните, что string — не самостоятельный класс, а простой синоним для следующего типа:</p>
<p><code>basic_string&lt;chaг,char_traits&lt;char&gt;,allocator&lt;char&gt; &gt;</code></p>
<p>Это связано с тем, что понятие строки С++ было обобщено до последовательности символов произвольного типа, обладающих произвольными характеристиками («traits») и хранящихся в памяти, выделенной произвольными распределителями. Все string-подобные объекты С++ в действительности являются специализациями шаблона basic_string, поэтому при диагностике ошибок, связанных с неверным использованием string, большинство компиляторов упоминает тип basic_string (некоторые компиляторы любезно включают в диагностику имя string, но большинство из них этого не делает). Нередко в диагностике указывается на принадлежность basic_string (а также вспомогательных шаблонов char_traits и allocator) к пространству имен std, поэтому в сообщениях об ошибках, связанных с использованием string, нередко упоминается тип</p>
<p><code>std::basic_string&lt;char.std::char_traits&lt;char&gt;.std::allocator&lt;char&gt; &gt;</code></p>
<p>Такая запись весьма близка к той, что встречается в приведенной выше диагностике, но разные компиляторы могут описывать string по-разному. На другой платформе STL ссылка на string выглядит иначе:</p>
<p><code>basic_string&lt;char,string_char_traits&lt;char&gt;,__default_alloc_template&lt;false,0&gt; &gt;</code></p>
<p>Имена string_char_traits и default_alloc_template не являются стандартными, но такова жизнь. Некоторые реализации STL отклоняются от Стандарта. Если вам не нравятся отклонения в текущей реализации STL, подумайте, не стоит ли перейти на другую реализацию. В совете 50 перечислены некоторые ресурсы, в которых можно найти альтернативные реализации.</p>
<p>Независимо от того, как тип string упоминается в диагностике компилятора, методика приведения диагностики к осмысленному минимуму остается той же: хитроумная конструкция с basic_string заменяется текстом «string». Если вы используете компилятор командной строки, задача обычно легко решается при помощи программы sed или сценарных языков типа Perl, Python или Ruby (пример сценария приведен в статье Золмана (Zolman) «Аn STL Error Message Decryptor for Visual С++» [26]). В приведенном примере производится глобальная замена фрагмента</p>
<p><code>std::basic_string&lt;char,struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt;&gt;</code></p>
<p>строкой string, в результате чего будет получено следующее сообщение:</p>
<p><code>example.срр(20):еггог С2664:'))thscall string::string(const class std::allocator&lt;char&gt;&amp;)':cannot convert parameter 1 from 'const int' to 'const class std::allocator&lt;char&gt;&amp;'</code></p>
<p>Из этого сообщения можно понять, что проблема связана с типом параметра, переданного конструктору string. Несмотря на загадочное упоминание allocator&lt;char&gt;, вам не составит труда просмотреть различные формы конструкторов string и убедиться в том, что ни одна из этих форм не вызывается только с аргументом размера.</p>
<p>Кстати, упоминание распределителя памяти (allocator<emphasis><strong>) </strong></emphasis>связано с наличием у всех стандартных контейнеров конструктора, которому передается только распределитель памяти. У типа string существуют три одноаргументных конструктора, но компилятор по какой-то причине решает, что вы пытаетесь передать именно распределитель. Его предположение ошибочно, а диагностика лишь сбивает с толку.</p>
<p>Что касается конструктора, получающего только распределитель памяти, — пожалуйста, не используйте его; он слишком часто приводит к появлению однотипных контейнеров с неэквивалентными распределителями памяти. Как правило, такая ситуация крайне нежелательна (более подробные объяснения приведены в совете 11).</p>
<p>Рассмотрим пример более сложной диагностики. Предположим, вы реализуете программу для работы с электронной почтой, которая позволяет ссылаться на адресатов не только по адресам, но и по синонимам — скажем, адресу президента США (<a l:href="mailto:president@whitehouse.gov">president@whitehouse.gov</a>) ставится в соответствие синоним «The Big Cheese». В такой программе может использоваться ассоциативный контейнер для отображения синонимов на адреса электронной почты и функция showEmailAddress, которая возвращает адрес для заданного синонима:</p>
<p><code>class NiftyEmailProgram {</code></p>
<p><code>private:</code></p>
<p><code>	typedef map&lt;string,string&gt;  NicknameMap;</code></p>
<p><code>	NicknameMap ncknames;</code></p>
<p><code>public:</code></p>
<p><code>	void showEmai1Address(const string&amp; nickname) const;</code></p>
<p><code>};</code></p>
<p>В реализации showEmailAddress потребуется найти адрес электронной почты, ассоциированный с заданным синонимом. Для этого может быть предложен следующий вариант:</p>
<p><code>void NiftyEmail Program::showEmailAddress(const string&amp; nickname) const</code></p>
<p><code> {</code></p>
<p><code>	NicknameMap::iterator =nicknames.find(nickname);</code></p>
<p><code>	if (i !=ncknames.end ())...</code></p>
<p><code>};</code></p>
<p>Компилятору такое решение не понравится. На то есть веская, но не очевидная причина. Чтобы помочь вам разобраться в происходящем, одна из платформ STL услужливо выдает следующее сообщение:</p>
<p><code>example.cpp(17):error С2440:'initializing': cannot convert from 'class std::_Tree&lt;class std::basic_string&lt;char.struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt;,struct std::pair&lt;class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt; const.class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt; &gt;.struct std::map&lt;class std::basic_string&lt;char.struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt;,class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt;,struct std::less&lt;class std::basc_string&lt;char.struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt; &gt;,class std::allocator&lt;class std::basc_string&lt;char,struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt; &gt; &gt;::_Kfn.struct std::less&lt;class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt; &gt;,class std::allocator&lt;class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt; &gt; &gt;::const_iterator' to 'class std::_Tree&lt;class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt;.struct std::pair&lt;class std::basic_string&lt;char.struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt; const.class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;,class std::allocator&lt;char&gt; &gt; &gt;.struct std::map&lt;class std::basic_string&lt;char.struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt;.class std: std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt;,struct std std::basic_string&lt;char,struct std::char_traits&lt;char&gt;.class std basic_st ring&lt;char,struct :less&lt;class :allocator&lt;char&gt; &gt;,struct std::less&lt;class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt; &gt;.class std::allocator&lt;class std std::char traits&lt;char&gt;.class std::allocator&lt;char&gt; : basic_string&lt;char. struct : Kfn,struct std::less&lt;class std::&lt;class std::basic_string&lt;char.struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt;.struct std std::char_traits&lt;char&gt;.class std: std::basic_string&lt;char.struct std :pair&lt;class std::basic_string&lt;char.struct allocator&lt;char&gt; &gt; const.class::char traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt;,struct std::map&lt;class std::basic_string&lt;char,struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt;.class std std::allocator&lt;char&gt; &gt;.struct std std::char_traits&lt;char&gt;,class std: std::basic_string&lt;char,struct std ::_Kfn,struct std::less&lt;class std std::allocator&lt;char&gt; &gt; &gt;.class std basic_string&lt;char.struct std::char_traits&lt;char&gt;.class less&lt;class std::basic_string&lt;char.struct allocator&lt;char&gt; &gt; &gt;,class std char_traits&lt;char&gt;,class std: basic string&lt;char.struct std :allocator&lt;class allocator&lt;char&gt; &gt; &gt; &gt; :char traits&lt;char&gt;,class :allocator&lt;class std::basic_string&lt;char.struct std::char_traits&lt;char&gt;.class std::allocator&lt;char&gt; &gt; &gt; &gt;:iterator'</code></p>
<p><code>No constructor could take the source type, or constructor overload resolution was ambiguous</code></p>
<p>Сообщение состоит из 2095 символов и выглядит довольно устрашающе, но я видал и похуже. Например, одна из моих любимых платформ STL однажды выдала сообщение из 4812 символов. Наверное, вы уже догадались, что я люблю ее совсем не за это.</p>
<p>Давайте немного сократим эту хаотическую запись и приведем ее к более удобному виду. Начнем с замены конструкции basic_string.. на string. Результат выглядит так:</p>
<p><code>example.cpp(17):error С2440:'initializing': cannot convert from 'class std::_Tree&lt;class string,struct std::pair&lt;class string const.class string &gt;,struct std::map&lt;class string,class string,struct std::less&lt;class string &gt;,class std::allocator&lt;class string &gt; &gt; ::_Kfn.struct std::less&lt;class string &gt;,class std::allocator&lt;class string &gt; &gt; ::const_iterator' to 'class std::_Tree&lt;class string.struct std::pair&lt;class string const,class string &gt;,struct std::map&lt;class string, class string.struct std::less&lt;class string&gt;, class std::allocator&lt;class string &gt; &gt;::_Kfn,struct std::less&lt;class string &gt;. class std::allocator&lt;class string &gt; &gt;: iterator'</code></p>
<p><code>No constructor could take the source type, or constructor overload resolution was ambiguous </code></p>
<p>Уже лучше. Осталось каких-нибудь 745 символов, можно начинать разбираться в сообщении. В глаза бросается упоминание шаблона <code>std::_Тгее</code>. В Стандарте ничего не сказано о шаблоне с именем Tree, но мы помним, что имена, начинающиеся с символа подчеркивания и прописной буквы, зарезервированы для авторов реализаций. Перед нами один из внутренних шаблонов, используемых в реализации некой составляющей STL.</p>
<p>Оказывается, практически во всех реализациях STL стандартные ассоциативные контейнеры (set, multiset, map и multimap) строятся на основе базовых шаблонов. По аналогии с тем, как при использовании string в диагностике упоминается тип basic_string, при работе со стандартными ассоциативными контейнерами часто выдаются сообщения с упоминанием базовых шаблонов. В данном примере этот шаблон называется _Tree, но в других известных мне реализациях встречались имена tree и _rb_tree, причем в последнем имени отражен факт использования красно-черных (Red-Black) деревьев, самой распространенной разновидности сбалансированных деревьев, встречающейся в реализациях STL.</p>
<p>В приведенном выше сообщении упоминается знакомый тип <code>std::map&lt;class string.class string,stuct std::less&lt;class string&gt;,class std::allocator&lt;class string&gt; &gt;</code>. Перед нами тип используемого контейнера <code>map</code>, если не считать типов функции сравнения и распределителя памяти (которые не были заданы при определении контейнера). Сообщение об ошибке станет более понятным, если заменить этот тип нашим вспомогательным определением <code>NicknameMap</code>. Результат:</p>
<p><code>example.срр(17):еггог С2440:'initalzing': cannot convert from 'class std::_Tree&lt;class string.struct std::pair&lt;class string const.class string &gt;,struct NicknameMap::_Kfn,struct std::less&lt;class string&gt;,class std::allocator&lt;class string &gt; &gt;::const_iterator' to 'class std::_Tree&lt;class string.struct std::pair&lt;class string const.class string &gt;.struct NicknameMap_Kfn.struct std::less&lt;class string &gt;, class std::allocator&lt;class string &gt; &gt;: iterator'</code></p>
<p><code>No constructor could take the source type, or constructor overload resolution was ambiguous</code></p>
<p>Сообщение стало короче, но его смысл остался туманным; нужно что-то сделать с <code>_Тгее</code>. Известно, что шаблон _Тгее зав4исит от реализации, поэтому узнать смысл его параметров можно только одним способом — чтением исходных текстов. Но зачем копаться в исходных текстах реализации STL, если это не нужно? Попробуем просто заменить все данные, передаваемые Tree, условным обозначением «НЕЧТО» и посмотрим, что из этого выйдет. Результат:</p>
<p><code>example.cpp(17):error С2440:'initalizing': cannot convert from 'class std::_Tree&lt;НЕЧТО::const_iterator' to 'class std::_Tree&lt;НЕЧТО:iterator'</code></p>
<p><code>No constructor could take the source type, or constructor overload resolution was ambiguous</code></p>
<p>А вот с <emphasis><strong>этим </strong></emphasis>уже можно работать. Компилятор жалуется на попытку преобразования const_iterator в iterator с явным нарушением правил константности.</p>
<p>Вернемся к исходному примеру; строка, вызвавшая гнев компилятора, выделена жирным шрифтом:</p>
<p><code>class NiftyEmailProgram {</code></p>
<p><code>private:</code></p>
<p><code>	typedef map&lt;string,string&gt; NicknameMap;</code></p>
<p><code>	NicknameMap nicknames;</code></p>
<p><code>public:</code></p>
<p><code>	void showEmailAddress(const string&amp; nickname) const;</code></p>
<p><code>};</code></p>
<p><code>void NiftyEmailProgram::showEmailAddress(const string&amp; nickname) const</code></p>
<p><code>{</code></p>
<p><code><strong>NicknameMap::iterator i =nicknames. find(nickname);</strong></code></p>
<p><code>if (i!=nicknames.end())...</code></p>
<p><code>}</code></p>
<p>Сообщение об ошибке можно истолковать лишь одним разумным образом — мы пытаемся инициализировать переменную i (типа iterator) значением типа const_iterator, возвращаемым при вызове map::find. Такая интерпретация выглядит несколько странно, поскольку find вызывается для объекта nicknames. Объект nicknames не является константным, поэтому функция find должна вернуть неконстантный итератор.</p>
<p>Взгляните еще раз. Да, объект nicknames объявлен как неконстантный тип map, но функция showEmalAddress является <emphasis><strong>константной, </strong></emphasis>а внутри константной функции все нестатические переменные класса становятся константными! Таким образом, внутри showEmalAddress объект nicknames является константным объектом map. Сообщение об ошибке внезапно обретает смысл. Мы пытаемся сгенерировать iterator для объекта map, который обещали не изменять. Чтобы исправить ошибку, необходимо либо привести i к типу const_iterator, либо объявить showEmalAddress неконстантной функцией. Вероятно, оба способа потребуют значительно меньших усилий, чем выяснение смысла сообщения об ошибке.</p>
<p>В этом совете были показаны некоторые текстовые подстановки, уменьшающие сложность сообщений об ошибках, но после непродолжительной практики вы сможете выполнять подстановки в голове. Я не музыкант, но мне рассказывали, что хорошие музыканты способны читать партитуру целиком, не присматриваясь к отдельным нотам. Опытные программисты STL приобретают аналогичные навыки. Они могут автоматически преобразовать конструкцию вида std::basic_string&lt;char, std::char_traits&lt;char&gt;,std::allocator&lt;char&gt; &gt; в string, нисколько не задумываясь над происходящим. Подобный навык разовьется и у вас, но до этих пор следует помнить, что диагностику компилятора почти всегда можно привести к вразумительному виду заменой длинных типов на базе шаблонов более короткими мнемоническими обозначениями. Во многих случаях для этого достаточно заменить расширенные определения типов именами, используемыми в программе. Именно это было сделано в приведенном примере, когда мы заменили std::map&lt;class string,class string,struct std::less&lt;class string&gt;,class std::allocator&lt;class string&gt; &gt; на NicknameMap.</p>
<p>Далее приведены некоторые рекомендации, которые помогут вам разобраться в сообщениях компилятора, относящихся к STL.</p>
<p>•Для контейнеров vector и string итераторы обычно представляют собой указатели, поэтому в случае ошибки с итератором в диагностике компилятора обычно указываются типы указателей. Например, если в исходном коде имеется ссылка на vector&lt;double&gt;:: iterator, в сообщении почти наверняка будет упоминаться указатель double*. Исключение составляет реализация STLport в отладочном режиме; в этом случае итераторы vector и string не являются указателями. За информацией о STLport и отладочном режиме обращайтесь к совету 50.</p>
<p>•Сообщения, в которых упоминаются back_insert_iterator, front_insert_iterator и insert_iterator, почти всегда означают, что ошибка была допущена при вызове back_inserter, front_inserter или inserter соответственно (back_inserter возвращает объект типа back_insert_iterator, front_inserter возвращает объект типа front_insert_iterator, a inserter возвращает объект типа insert_iterator; за информацией об этих типах обращайтесь к совету 30). Если эти функции не вызывались в программе, значит, они были вызваны из других функций (косвенно или явно).</p>
<p>•Сообщения с упоминаниями binder1st и binder2nd обычно свидетельствуют об ошибке при использовании bind1st и bind2nd (bind1st возвращает объект типа binder1st, a bind2nd возвращает объект типа binder2nd).</p>
<p>•Итераторы вывода (например, ostream_iterator и ostream_buf_iterator — см. совет 29, а также итераторы, возвращаемые back_inserter, front_inserter и inserter) выполняют свои операции вывода или вставки внутри операторов присваивания, поэтому ошибки, относящиеся к этим типам итераторов, обычно приводят к появлению сообщений об ошибке внутри операторов присваивания, о которых вы и понятия не имеете. Чтобы понять, о чем идет речь, попробуйте откомпилировать следующий фрагмент:</p>
<p><code>vector&lt;string*&gt; v;// Попытка вывода содержимого</code></p>
<p><code>copy(v.begin(),v.end(),// контейнера указателей string*</code></p>
<p><code>ostream_iterator&lt;string&gt;(cout."\n")); // как объектов string</code></p>
<p>•Если полученное сообщение об ошибке исходит из реализации алгоритма STL (то есть если код, в котором произошла ошибка, находится в &lt;algoritm&gt;), вероятно, проблема связана с типами, которые вы пытаетесь передать этому алгоритму. Пример — передача итераторов неправильной категории. Попробуйте откомпилировать следующий фрагмент:</p>
<p><code>list&lt;int&gt;::iterator 11,12; // Передача двусторонних итераторов</code></p>
<p><code>sort(11.2);// алгоритму, которому необходимы итераторы</code></p>
<p><code>// произвольного доступа</code></p>
<p>•Если вы используете стандартный компонент STL (например, контейнер vector или string, алгоритм for_each), а компилятор утверждает, что он понятия не имеет, что имеется в виду, скорее всего, вы забыли включить необходимый заголовочный файл директивой #include. Как объясняется в совете 48, эта проблема может нарушить работоспособность кода, успешно компилировавшегося в течение некоторого времени, при переносе его на другую платформу.</p>
</section>
<section>
<title>
<p>Совет 50. Помните о web-сайтах, посвященных STL</p>
</title>
<section>
<p>Интернет богат информацией об STL. Если ввести в любой поисковой системе запрос «STL», вы получите сотни ссылок, часть из которых даже будет содержать полезную информацию. Впрочем, большинство программистов STL в поисках не нуждается и хорошо знает следующие сайты:</p>
<p>•сайт SGI STL, <a l:href="http://www.sgi.com/tech/stl">http://www.sgi.com/tech/stl</a>;</p>
<p>•сайт STLport, <a l:href="http://stlport.org/">http://stlport.org</a>;</p>
<p>•сайт Boost, <a l:href="http://www.boost.org/">http://www.boost.org</a>.</p>
<p>Ниже я постараюсь объяснить, почему эти сайты заслуживают вашего внимания.</p>
</section>
<section>
<title>
<p>Сайт SGI STL</p>
</title>
<p>Web-сайт SGI STL не случайно находится в начале списка. На нем имеется подробная документация по всем компонентам STL. Многие программисты рассматривают его как основной источник электронной документации незав4исимо от используемой платформы STL. Документацию собрал Мэтт Остерн (Matt Austern), который позднее дополнил ее и представил в книге «Generic Programming and the STL» [4]. Материал не сводится к простому описанию компонентов STL. Например, описание потоковой безопасности контейнеров STL (см. совет 12) основано на материалах сайта SGI STL.</p>
<p>На сайте SGI программисту предлагается свободно распространяемая реализация STL. Она была адаптирована лишь для ограниченного круга компиляторов, но поставка STL легла в основу распространенной поставки STLport, описанной ниже. Более того, в реализацию STL от SGI входят некоторые нестандартные компоненты, делающие программирование в STL не только более мощным и гибким, но и более интересным. Некоторые из них стоит выделить.</p>
<p>•<strong>Хэшированные ассоциативные контейнеры</strong> hash_set, hash_multiset, hash_map и hash_multimap. За дополнительной информацией об этих контейнерах обращайтесь к совету 25.</p>
<p>•<strong>Односвязный список</strong> slist. Контейнер slist реализован наиболее стандартным образом, а итераторы указывают на те узлы списка, на которые они и должны указывать. К сожалению, этот факт оборачивается дополнительными затратами при реализации функций insert и erase, поскольку обе функции должны модифицировать указатель на следующий узел списка в узле, <emphasis><strong>предшествующем </strong></emphasis>тому, на который указывает итератор. В двусвязном списке (например, в стандартном контейнере list) это не вызывает проблем, но в односвязном списке возврат к предыдущему узлу является операцией с линейной сложностью. В контейнере slist из реализации SGI функции insert и erase выполняются с линейной сложностью вместо постоянной, что является существенным недостатком. В реализации SGI эта проблема решается при помощи нестандартных (но зато работающих с постоянной сложностью) функций insert_after и erase_after. В сопроводительной документации говорится:</p>
<p>«<emphasis>...Если окажется, что функции insert_after и erase_after плохо подходят для ваших целей, и вам часто приходится вызывать функции insert и erase в середине списка, вероятно, вместо slist лучше воспользоваться контейнером list</emphasis>».</p>
<p>В реализацию Dinkumware также входит односвязный список slist, но в нем используется другая архитектура итераторов, сохраняющая постоянную сложность при вызовах insert и erase. За дополнительной информацией о Dimkumware обращайтесь к приложению Б.</p>
<p>•<strong>Контейнер rope</strong>, аналог string для очень больших строк. В документации SGI контейнер rope описывается так:</p>
<p>«Контейнер rope представляет собой масштабированную разновидность string: он предназначен для эффективного выполнения операций со строками в целом. Затраты времени на такие операции, как присваивание, конкатенация и выделение подстроки, практически не зависят от длины строки. В отличие от строк С, контейнер rope обеспечивает разумное представление для очень длинных строк (например, содержимого буфера текстового редактора или сообщений электронной почты)».</p>
<p>Во внутреннем представлении контейнер rope реализуется в виде дерева подстрок с подсчетом ссылок, при этом каждая строка хранится в виде массива char. Одна из интересных особенностей интерфейса rope заключается в том, что функции begin и end всегда возвращают тип const_iterator. Это сделано для предотвращения операций, изменяющих отдельные символы. Такие операции обходятся слишком дорого. Контейнер rope оптимизирован для операций с текстом в целом или большими фрагментами (присваивание, конкатенация и выделение подстроки); операции с отдельными символами выполняются неэффективно.</p>
<p>•<strong>Различные нестандартные объекты функций и адаптеры</strong>. Некоторые классы функторов из исходной реализации HP STL не вошли в Стандарт С++. Опытным мастерам старой школы больше всего не хватает функторов select1st и select2nd, чрезвычайно удобных при работе с контейнерами map и multimap. Функтор select1st возвращает первый компонент объекта pair, а функтор select2nd возвращает второй компонент объекта pair. Пример использования этих нестандартных шаблонов классов функторов:</p>
<p><code>map&lt;int,string&gt; m;</code></p>
<p><code>// Вывод всех ключей map в cout</code></p>
<p><code>transform(m.begin(),m.end(),</code></p>
<p><code>ostream_iterator&lt;int&gt;(cout,"\n"),</code></p>
<p><code><strong>select1st&lt;map&lt;int,string&gt;::value_type&gt;());</strong></code></p>
<p><code>// Создать вектор и скопировать в него</code></p>
<p><code>// все ассоциированные значения из map</code></p>
<p><code>vector&lt;string&gt; v:</code></p>
<p><code>transforms.begin(),m.end() ,back_inserter(v),</code></p>
<p><code><strong>select2nd&lt;map&lt;int,string&gt;::value_type&gt;());</strong></code></p>
<p>Как видите, функторы select1st и select2nd упрощают использование алгоритмов в ситуациях, где обычно приходится писать собственные циклы (см. совет 43). С другой стороны, вследствие нестандартности функторов вас могут обвинить в написании непереносимого и вдобавок плохо сопровождаемого кода (см. совет 47).</p>
<p>Настоящих фанатов STL это нисколько не волнует. Они считают, что отсутствие select1st и select2nd в Стандарте само по себе является вопиющей несправедливостью.</p>
<p>К числу нестандартных объектов функций, входящих в реализацию STL, также принадлежат объекты identity, project1st, project2nd, compose1 и compose2. Информацию о них можно найти на сайте, хотя пример использования compose2 приводился на с. 172 настоящей книги. Надеюсь, я убедил вас в том, что посещение web-сайта SGI принесет несомненную пользу.</p>
<p>Реализация библиотеки от SGI выходит за рамки STL. Первоначально ставилась цель разработки полной реализации стандартной библиотеки С++ за исключением компонентов, унаследованных из С (предполагается, что у вас в распоряжении уже имеется стандартная библиотека С). По этой причине с сайта SGI также стоит получить реализацию библиотеки потоков ввода-вывода С++. Как следует ожидать, эта реализация хорошо интегрируется с реализацией STL от SGI, но при этом по быстродействию она превосходит многие аналогичные реализации, поставляемые с компиляторами С++.</p>
</section>
<section>
<title>
<p>Сайт STLport</p>
</title>
<p>Главная отличительная особенность STLport заключается в том, что эта модифицированная версия реализации STL от SGI (включая потоки ввода-вывода и т. д.) была перенесена более чем на 20 компиляторов. STLport, как и библиотека SGI, распространяется бесплатно. Если ваш код должен работать сразу на нескольких платформах, вы избавите себя от множества хлопот, если возьмете за основу унифицированную реализацию STLport и будете использовать ее со всеми компиляторами.</p>
<p>Большинство изменений кода SGI в реализации STLport связано с улучшением переносимости, однако STLport является единственной известной мне реализацией, в которой предусмотрен «отладочный режим» для диагностики случаев неправильного использования STL — компилируемых, но приводящих к непредсказуемым последствиям во время работы программы. Например, в совете 30 распространенная ошибка записи за концом контейнера поясняется следующим примером:</p>
<p><code>int transmogrify(int х); // Функция вычисляет некое новое значение</code></p>
<p><code>// по переданному параметру х</code></p>
<p><code>vector&lt;int&gt; values;</code></p>
<p><code>// Заполнение вектора values данными</code></p>
<p><code>vector&lt;int&gt; results;</code></p>
<p><code>transform(values.begin(), // Попытка записи за концом results! 	values.end(),</code></p>
<p><code>	 results.end,</code></p>
<p><code>	transmogrify);</code></p>
<p>Этот фрагмент компилируется, но во время выполнения работает непредсказуемо. Если вам повезет, проблемы возникнут при вызове transform, и отладка будет относительно элементарной. Гораздо хуже, если вызов transform испортит данные где-то в другом месте адресного пространства, но это обнаружится лишь позднее. В этом случае определение причины порчи данных становится задачей — как бы выразиться? — <emphasis><strong>весьма нетривиальной.</strong></emphasis></p>
<p>Отладочный режим STLport значительно упрощает эту задачу. При выполнении приведенного выше вызова transform выдается следующее сообщение (предполагается, что реализация STLport установлена в каталоге C:\STLport):</p>
<p><code>C:\STLport\stlport\stl\debug\_iterator.h:265 STL assertion failure: _Dereferenceable(*this)</code></p>
<p>На этом программа прекращает работу, поскольку в случае ошибки отладочный режим STLport вызывает abort. Если вы предпочитаете, чтобы вместо этого инициировалось исключение, STLport можно настроить и на этот режим.</p>
<p>Честно говоря, приведенное сообщение об ошибке менее понятно, чем хотелось бы, а имя файла и номер строки относятся к внутренней проверке условия STL, а не к строке с вызовом transform, но это все же значительно лучше пропущенного вызова transform и последующих попыток разобраться в причинах разрушения структур данных. В отладочном режиме STLport остается лишь запустить программу-отладчик, вернуться по содержимому стека к написанному вами коду и определить, что же произошло. Строка, содержащая ошибку, обычно находится достаточно легко.</p>
<p>Отладочный режим STLport распознает широкий спектр стандартных ошибок, в том числе передачу алгоритмам недопустимых интервалов, попытки чтения из пустого контейнера, передачу итератора одного контейнера в качестве аргумента функции другого контейнера и т. д. Волшебство основано на взаимном отслеживании итераторов и контейнеров. При наличии двух итераторов это позволяет проверить, принадлежат ли они одному контейнеру, а при модификации контейнера — определить, какие итераторы становятся недействительными.</p>
<p>В отладочном режиме реализация STLport использует специальные реализации итераторов, поэтому итераторы vector и string являются объектами классов, а не низкоуровневыми указателями. Таким образом, использование STLport и компиляция в отладочном режиме помогают убедиться в том, что ваша программа не игнорирует различия между указателями и итераторами для соответствующих типов контейнеров. Одной этой причины может оказаться достаточно для того, чтобы познакомиться с отладочным режимом STLport.</p>
<p>•</p>
</section>
<section>
<title>
<p>Сайт Boost</p>
</title>
<p>В 1997 году завершился процесс, приведший к появлению Международного стандарта С++. Многие участники были разочарованы тем, что возможности, за которые они выступали, не прошли окончательный отбор. Некоторые из этих участников были членами самого Комитета, поэтому они решили разработать основу для дополнения стандартной библиотеки во время второго круга стандартизации. Результатом их усилий стал сайт Boost, который был призван «предоставить бесплатные библиотеки С++. Основное внимание уделяется переносимым библиотекам, соответствующим Стандарту С++». За этой целью кроется конкретный мотив:</p>
<p>«По мере того как библиотека входит в "повседневную практику", возрастает вероятность того, что кто-нибудь предложит ее для будущей стандартизации. Предоставление библиотеки на сайт <a l:href="http://Boost.org/">Boost.org</a> является одним из способов создания "повседневной практики"...».</p>
<p>Иначе говоря, Boost предлагается в качестве механизма, помогающего отделить плевелы от зерен в области потенциальных дополнений стандартной библиотеки С++. Вполне достойная миссия, заслуживающая нашей благодарности.</p>
<p>Также стоит обратить внимание на подборку библиотек, находящихся на сайте Boost. Я не стану описывать ее здесь хотя бы потому, что к моменту выхода книги на сайте наверняка появятся новые библиотеки. Для пользователей STL особый интерес представляют две библиотеки. Первая содержит шаблон shared_ptr, умный указатель с подсчетом ссылок, который в отличие от указателя auto_ptr из стандартной библиотеки может храниться в контейнерах STL (см. совет 8). Библиотека умных указателей также содержит шаблон shared_array, умный указатель с подсчетом ссылок для работы динамическими массивами, но в совете 13 вместо динамических массивов рекомендуется использовать контейнеры vector и string; надеюсь, приведенные аргументы покажутся вам убедительными.</p>
<p>Поклонники STL также оценят богатый ассортимент библиотек, содержащих объекты функций и другие вспомогательные средства. В этих библиотеках заново спроектированы и реализованы некоторые концепции, заложенные в основу объектов функций и адаптеров STL, в результате чего были сняты некоторые искусственные ограничения, снижающие практическую полезность стандартных функторов. В частности, при попытках использовать bind2nd с функциями mem_fun и mem_fun_ref (см. совет 41) для привязки объекта к параметрам функции класса выясняется, что при передаче параметра по ссылке код, скорее всего, компилироваться не будет. Аналогичный результат достигается использованием not1 и not2 с ptr_fun и функцией, получающей параметр по ссылке. Причина в обоих случаях заключается в том, что в процессе специализации шаблона многие платформы STL генерируют «ссылку на ссылку», но в С++ такая конструкция запрещена (в настоящее время Комитет по стандартизации рассматривает возможность внесения изменений в Стандарт для решения этой проблемы). Пример проблемы «ссылки на ссылку»:</p>
<p><code>class Widget {</code></p>
<p><code>public:</code></p>
<p><code>int readStream(istream&amp; stream);// Функции readStream</code></p>
<p><code>		// параметр передается</code></p>
<p><code>};	//по ссылке</code></p>
<p><code>vector&lt;Widget*&gt; vw;</code></p>
<p><code>for_each(	//Большинство платформ STL</code></p>
<p><code>	vw.begin(),vw.end(),	//при этом вызове</code></p>
<p><code>	bind2nd(mem_fun(&amp;Widget::readStream),cin) //пытается сгенерировать</code></p>
<p><code>);//ссылку на ссылку.</code></p>
<p><code>//Фрагмент не компилируется!</code></p>
<p>Объекты функций Boost решают эту и многие другие проблемы, а также значительно повышают выразительность объектов функций.</p>
<p>Если вы интересуетесь потенциальными возможностями объектов функций STL и хотите познакомиться с ними поближе, поскорее посетите сайт Boost. Если объекты функций вас пугают и вы считаете, что они существуют только для умиротворения малочисленных апологетов Lisp, вынужденных программировать на С++, все равно посетите сайт Boost. Библиотеки объектов функций Boost важны, но они составляют лишь малую часть полезной информации, находящейся на сайте.</p>
</section>
</section>
</section>
<section>
<title>
<p>Литература</p>
</title>
<section>
<p>В книге имеются ссылки на большинство публикаций, перечисленных ниже, хотя многие ссылки присутствуют лишь в разделе «Благодарности». Публикации, которые в книге не упоминаются, помечены кружком вместо цифры.</p>
<p>Адреса URL ненадежны, поэтому я некоторое время сомневался, стоит ли приводить их в этом разделе. В итоге я решил, что даже если URL станет недействительным, предыдущее местонахождение документа поможет вам найти его по новому адресу.</p>
</section>
<section>
<title>
<p>Книги, написанные мной</p>
</title>
<p>[1] Scott Meyers, «Effective С++: 50 Specific Ways to Improve Your Programs and Designs» (second edition), Addison-Wesley, 1998, ISBN 0-201-92488-9. Также присутствует на компакт-диске «Effective С++» (см. далее).</p>
<p>[2] Scott Meyers, «More Effective С++: 35 New Ways to Improve Your Programs ацd Designs» (second edition), Addison-Wesley, 1996, ISBN 0-201-63371-Х. Также присутствует на компакт-диске «Effective С++» (см. далее).</p>
<p>• Scott Meyers, «Effective С++ CD: 85 Specific Ways to Improve Your Programs and Designs» (second edition), Addison-Wesley, 1999, ISBN 0-201-31015-5. Содержит материалы обеих книг, несколько журнальных статей по теме и кое-какие новинки из области электронных публикаций. За компакт-диском обращайтесь по адресу: <a l:href="http://meyerscd.awl.com/">http://meyerscd.awl.com/</a>. Информацию о новинках можно найти по адресам: <a l:href="http://zing.ncsl.nist.goc/hfweb/proceedings/meyers-">http://zing.ncsl.nist.goc/hfweb/proceedings/meyers-</a>jones/и http://www.rm'crosoft.com/Mind/1099/browsing/browsing.htm.</p>
</section>
<section>
<title>
<p>Книги, написанные другими авторами</p>
</title>
<p>[3] Nicolai М. Josuttis, «The С++ Standard Library: A Tutorial and Reference», Addison-Wesley, 1999, ISBN 0-201-37926-0. Незаменимая книга, которая должна быть у каждого программиста С++.</p>
<p>[4] Matthew Н. Austern, «Generic Programming and the STL», Addison-Wesley, 1999, ISBN 0-201-30956-4. Фактически представляет собой печатную версию материалов web-сайта SGI STL, <a l:href="http://www.sgi.com/tech/stl">http://www.sgi.com/tech/stl</a>.</p>
<p>[5] ISO/IEC, «International Standard, Programming Languages — С++», ISO/IEC 14882:1998(E), 1998. Официальный документ с описанием С++. Распространяется комитетом ANSI в формате PDF за $18 по адресу <a l:href="http://webstore/">http://webstore</a>. <a l:href="http://ansi.org/ansidocstore/default.asp">ansi.org/ansidocstore/default.asp</a>.</p>
<p>[6] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, «Design Patterns: Elements of Reusable Object-Oriented Software», Addison-Wesley, 1995, ISBN 0-201 -63361-2<a l:href="#n4" type="note">[4]</a>. Также распространяется на компакт-диске «Design Patterns CD», Addison-Wesley, 1998, ISBN 0-201-63498-8. Наиболее авторитетное руководство по идиомам проектирования. Каждый программист С++ должен знать описанные идиомы и держать под рукой эту книгу или компакт-диск.</p>
<p>[7] Bjarne Stroustrup, «The С++ Programming Language» (third edition), Addison-Wesley, 1997, ISBN 0-201-88954-4. Идиома «заквата ресурсов при инициализации», упоминаемая в совете 12, рассматривается в разделе 14.4.1 этой книги, а код из совета 36 приведен на с. 530.</p>
<p>[8] Herb Sutter, «Exceptional С++: 47 Engineering Puzzles, Programming Problems and Solutions», Addison-Wesley, 2000, ISBN 0-201-61562-2. Достойное дополнение к моей серии «Effective...». Я бы высоко оценил эту книгу в любом случае, даже если бы Херб не попросил меня написать к ней предисловие.</p>
<p>[9] Herb Sutter, «More Exceptional С++: 40 More Engineering Puzzles, Programming Problems and Solutions», Addison-Wesley, 2001, ISBN 0-201-70434-X. Судя по предварительной версии, которую я видел, эта книга ничуть не хуже предыдущей.</p>
<p>[10] Dov Bulka, David Mayhew, «Efficient С++: Performance Programming Techniques», Addison-Wesley, 2000, ISBN 0-201-37950-3. Единственная и поэтому лучшая книга, посвященная вопросам эффективности в С++.</p>
<p>[И] Matt Austern, «How to Do Case-Insensitive String Comparison», С++ Report, май 2000 г. Эта статья настолько полезна, что она воспроизводится в приложении А настоящей книги.</p>
<p>[12] Herb Sutter, «When Is a Container Not A Container?», С++ Report, май 1999 г. Статья доступна по адресу <a l:href="http://www.gotw.ca/publications/mUl09.htm">http://www.gotw.ca/publications/mUl09.htm</a>. Материал пересмотрен и дополнен в совете 6 книги «More Exceptional С++» [9].</p>
<p>[13] Herb Sutter, «Standard Library News: sets and maps», С++ Report, октябрь</p>
<p>1999г. Статья доступна по адресу <a l:href="http://www.gotw.ca/publications/millll.htm">http://www.gotw.ca/publications/millll.htm</a>. Материал пересмотрен и дополнен в совете 8 книги «More Exceptional С++» [9].</p>
<p>[ 14] Nicolai М. Josuttis, «Predicates vs. Function Objects», С++ Report, июнь 2000 г.</p>
<p>[15] Matt Austern, «Why You Shouldn't Use set - and What to Use Instead*, С++ Report, апрель 2000 г.</p>
<p>[16] P.J. Plauger, «HashTables», C/C++ Users Journal, ноябрь 1999 г. В статье описан подход реализации Dinkumware к хэшированным контейнерам (см. совет 25) и его отличия от альтернативных решений.</p>
<p>[17] Jack Reeves, «STL Gotcha's», С++ Report, январь 1997 г. Статья доступна по адресу <a l:href="http://www.bleading-edge.com/Publications/C++Report/v9701/abstract.htm">http://www.bleading-edge.com/Publications/C+</a><a l:href="http://www.bleading-edge.com/Publications/C++Report/v9701/abstract.htm">+Report/v9701/abstract.htm</a>.</p>
<p>[18] Jack Reeves, «Using Standard string in the Real World, Part 2», С++ Report, январь 1999 г. Статья доступна по адресу <a l:href="http://www.bleading-edge.com/Publications/">http://www.bleading-edge.com/Publications/</a> C++Report/v9901/abstract.htm.</p>
<p>[19] Andrei Alexandrescu, «Traits: The if-then-else of Types», С++ Report, апрель</p>
<p>2000г. Статья доступна по адресу <a l:href="http://www.creport.com/html/from_pages/">http://www.creport.com/html/from_pages/</a> view_recent_articles_c.cfm?ArticleID=402.</p>
<p>[20] Herb Sutter, «Optimizations That Aren't (In a Multithreaded World)», C/C++ Users Journal, июнь 1999 г. Статья доступна по адресу <a l:href="http://www.gotw.ca/">http://www.gotw.ca/</a> publications/optimizations.htm. Материал пересмотрен и дополнен в совете 16 книги «More Exceptional С++» [9]. ,</p>
<p>[21] Web-сайт SGI STL, <a l:href="http://www.sgi.com/tech/stl">http://www.sgi.com/tech/stl</a>. В совете 50 кратко описано содержимое этого сайта. Страница, посвященная потоковой безопасности контейнеров STL (взятая за основу при написании совета 12), накодится по адресу <a l:href="http://www.sgi.com/tech/stl/thread_safety.html">http://www.sgi.com/tech/stl/thread_safety.html</a>.</p>
<p>[22] Web-сайт Boost, <a l:href="http://www.boost.org/">http://www.boost.org/</a>. Содержимое сайта кратко описано в совете 50.</p>
<p>[23] Nicolai М. Josuttis, «User-Defined Allocator», <a l:href="http://www.josuttis.com/cppcode/">http://www.josuttis.com/cppcode/</a> allocator.html. Страница является частью сайта, посвященного превосходной книге Джосаттиса о стандартной библиотеке С++ [3].</p>
<p>[24] Matt Austern, «The Standard Librarim: What Are Allocators Good For?», форум экспертов C/C++ Users Journal (сетевое дополнение к журналу), ноябрь 2000 г., <a l:href="http://www.cuj.com/experts/1812/austern.htm">http://www.cuj.com/experts/1812/austern.htm</a>. Найти толковую информацию о распределителях памяти нелегко. Статья дополняет материал советов 10 и 11. Кроме того, в ней приведен пример реализации распределителя памяти.</p>
<p>[25] Klaus Kreft, Angelika Langer, «A Sophisticated Implementation of User-Defined Inserters and Extractors», С++ Report, февраль 2000 г.</p>
<p>[26] Leor^Zolman, «An STL Error Message Decryptor for Visual С++», C/C++ Users Journal, июль 2001 г. Статья и описанная в ней программа доступны по адресу <a l:href="http://www.bdsoft.com/tools/stlfilt.html">http://www.bdsoft.com/tools/stlfilt.html</a>.</p>
<p>[27] Bjarne Stroustrup, «Sixteens Ways to Stack a Cat», С++ Report, октябрь 1990 г. Статья доступна по адресу <a l:href="http://www.csdn/">http://www.csdn</a>.net/dev/C&amp;C++/Document/Stackcat.pdf.</p>
<p>•Herb Sutter, «Guru of the Week #74: Uses and Abuses of vector», сентябрь 2000 г. Задача с прилагаемым решением помогает разобраться в некоторых аспектак использования vector, в том числе в различиях между размером и емкостью (см. совет 14). Кроме того, в статье обсуждаются преимущества алгоритмов перед циклическими вызовами (см. совет 43).</p>
<p>•Matt Austern, «The Standard Librarian: Bitsets and Bit Vectors?», форум экспертов C/C++ Users Journal (сетевое дополнение к журналу), май 2001 г., <a l:href="http://www.cuj.com/expeits/1905/austern.htm">http://www.cuj.com/expeits/1905/austern.htm</a>. В статье описаны контейнеры bitset, которые сравниваются с vector&lt;bool&gt;, — эти темы кратко рассматриваются в совете 18.</p>
</section>
<section>
<title>
<p>Ошибки и опечатки</p>
</title>
<p>•Список ошибок и опечаток в книге «Effective С++»: <a l:href="http://www.aristeia.com/">http://www.aristeia.com/</a> BookErrata/ec++2e-errata.html.</p>
<p>[28] Список ошибок и опечаток в книге «More Effective С++»: <a l:href="http://www.aristeia/">http://www.aristeia</a>. com/BookErrata/mec-H-errata.html.</p>
<p>•Список ошибок и опечаток на компакт-диске «Effective С++»: <a l:href="http://www/">http://www</a>. <a l:href="http://aristeia.com/BookErrata/cdle-errata.html">aristeia.com/BookErrata/cdle-errata.html</a>.</p>
<p>[29] Обновления «More Effective С++»* относящиеся к auto_ptr: <a l:href="http://www.awl/">http://www.awl</a>. com/cseng/titles/0-201-63371-X/auto_ptr.html.</p>
</section>
</section>
<section>
<title>
<p>Локальные контексты</p>
</title>
<section>
<p>В совете 35 приведена реализация сравнения строк без учета регистра символов с применением алгоритмов mismatch и lexicographical_compare, но в нем также указано, что полноценное решение должно учитывать локальный контекст. Книга посвящена STL, а не вопросам интернационализации, поэтому локальным контекстам в ней не нашлось места. Тем не менее, Мэтт Остерн, автор книги «Generic Programming and the STL» [4], посвятил этой теме статью в майском номере журнала «С++ Report» [И]. Текст этой статьи приведен в настоящем приложении. Я благодарен Мэтту и фирме lOlcommunications за то, что они разрешили мне это сделать.</p>
</section>
<section>
<title>
<p>Сравнение строк без учета регистра символов</p>
</title>
<p><emphasis>Мэтт Остерн</emphasis></p>
<p>Если вам когда-либо доводилось писать программы, в которых используются строки (а кому, спрашивается, не доводилось?), скорее всего, вы встречались с типичной ситуацией — две строки, различающиеся только регистром символов, должны были интерпретироваться как равные. В этих случаях требовалось, чтобы операции сравнения — проверка равенства, больше-меньше, выделение подстрок, сортировка — игнорировали регистр символов. Программисты очень часто спрашивают, как организовать подобные операции средствами стандартной библиотеки С++. На этот вопрос существует огромное количество ответов, многие из которых неверны.</p>
<p>Прежде всего необходимо избавиться от мысли о написании класса, сравнивающего строки без учета регистра. Да, с технической точки зрения это более или менее возможно. Тип std:: string стандартной библиотеки в действительности является синонимом для типа std::basic_string&lt;char,std::char_trais&lt;char&gt;,sd:: allocator&lt;char&gt; &gt;. Операции сравнения определяются вторым параметром; передавая второй параметр с переопределенными операциями «равно» и «меньше», можно специализировать <strong>basic_string </strong>таким образом, что операции &lt; и = будут игнорировать регистр символов. Такое решение возможно, но игра не стоит свеч.</p>
<p><emphasis><strong>Вы не сможете выполнять операции ввода-вывода </strong></emphasis>или это потребует больших дополнительных хлопот. Классы ввода-вывода стандартной библиотеки (такие как <strong>std:: basic_istream </strong>и <strong>std::basic_ostream) </strong>специализируются по двум начальным параметрам <strong>std::basic_string (</strong>а <strong>std::ostream </strong>всего лишь является синонимом для <strong>std::basic_ostreanKchar,char_traits&lt;char&gt; &gt;). </strong>Параметры характеристик <strong>(traits) </strong>должны совпадать. Если вы используете строки типа <strong>std:: basic_string&lt;char, my_traits_class&gt;, </strong>то для вывода строк должен использоваться тип <strong>std::basic_ostream&lt;char,my_traits_class&gt;. </strong>Стандартные потоки <strong>cin </strong>и <strong>cout </strong>для этой цели не подойдут.</p>
<p><emphasis><strong>Игнорирование регистра символов является не свойством объекта, а лишь контекстом его использования. </strong></emphasis>Вполне возможно, что в одном контексте строки должны интерпретироваться с учетом регистра, а в другом контексте регистр должен игнорироваться (например, при установке соответствующего режима пользователем).</p>
<p><emphasis><strong>Решение не соответствует канонам. </strong></emphasis>Класс <strong>char_traits, </strong>как и все классы характеристик<a l:href="#n5" type="note">[5]</a>, прост, компактен и не содержит информации состояния. Как будет показано ниже, правильная реализация сравнений без учета регистра не отвечает ни одному из этих критериев.</p>
<p><emphasis><strong>Этого вообще не достаточно. </strong></emphasis>Даже если все функции <strong>basic_string </strong>будут игнорировать регистр, это никак не отразится на использовании внешних обобщенных алгоритмов, таких как <strong>std::search </strong>и <strong>std::find_end. </strong>Кроме того, такое решение перестает работать, если по соображениям эффективности перейти от контейнера объектов <strong>basicstring </strong>к таблице строк.</p>
<p>Более правильное решение, которое лучше соответствует архитектуре стандартной библиотеки, заключается в том, чтобы игнорировать регистр символов только в тех случаях, когда это действительно необходимо. Не стоит возиться с такими функциями контейнера <strong>string, </strong>как <strong>string::find_first </strong>или <strong>string::rfind; </strong>они лишь дублируют функциональные возможности, уже поддерживаемые внешними обобщенными алгоритмами. С другой стороны, алгоритмы обладают достаточной гибкостью, что позволяет реализовать в них поддержку сравнений строк без учета регистра. Например, чтобы отсортировать коллекцию строк без учета регистра, достаточно передать алгоритму працильный объект функции сравнения:</p>
<p>std::sort(С.begin(), С.end().compare_wi thout_case);</p>
<p>Написанию таких объектов и посвящена эта статья.</p>
</section>
<section>
<title>
<p>Первая попытка</p>
</title>
<p>Существует несколько способов упорядочения слов по алфавиту. Зайдите в книжный магазин и посмотрите, как расставлены книги на полках. Предшествует ли имя</p>
<p>1 См. статью Александреску A. (Andrei Alexandrescu) в майском номере «С++ Report» за 2000 г. [19].</p>
<p>Mary McCarthy имени Bernard Malamud или следует после него? (В действительности это лишь вопрос привычки, я встречал оба варианта.) Впрочем, простейший способ сравнения строк хорошо знаком нам по школе: речь идет о лексикографическом, или «словарном», сравнении, основанном на последовательном сравнений отдельных символов двух строк.</p>
<p>Лексикографический критерий сравнения может оказаться неподходящим для некоторых специфических ситуаций. Более того, единого критерия вообще не существует — например, имена людей и географические названия иногда сортируются по разным критериям. С другой стороны, в большинстве случаев лексикографический критерий подходит, поэтому он был заложен в основу механизма строковых сравнений в С++. Строка представляет собой последовательность символов. Если объекты х и у относятся к типу std:: string, то выражение х&lt;у эквивалентно выражению</p>
<p><code>std:: lexicographical_compare(x.begin(),x.end(), y.begin(), y.end())</code></p>
<p>В приведенном выражении алгоритм lexicographical_compare сравнивает отдельные символы оператором &lt;, однако существует другая версия lexicographical_ compare, позволяющая задать пользовательский критерий сравнения символов. Она вызывается с пятью аргументами вместо четырех; в последнем аргументе передается объект функции, двоичный предикат, определяющий, какой из двух символов предшествует другому. Таким образом, для сравнения строк без учета регистра на базе lexicographical_compare достаточно объединить этот алгоритм с объектом функции, игнорирующим различия в регистре символов.</p>
<p>Распространенный принцип сравнения двух символов без учета регистра заключается в том, чтобы преобразовать оба символа к верхнему регистру и срац-нить результаты. Ниже приведена тривиальная формулировка этой идеи в виде объекта функции С++ с использованием хорошо известной функции toupper из стандартной библиотеки С:</p>
<p><code>struct lt_nocase </code></p>
<p><code> :public std::binary_function&lt;char,char,bool&gt;{</code></p>
<p><code>bool operator() (char x.char y) const{</code></p>
<p><code>return std::toupper(static_cast&lt;unsigned char&gt;(x))&lt;</code></p>
<p><code>std::toupper(static_cast&lt;unsigned char&gt;(y));</code></p>
<p><code>}</code></p>
<p><code>};</code></p>
<p>«<code>У каждой сложной задачи есть решение простое, элегантное и... неправильное</code>» Авторы книг С++ обожают этот класс за простоту и наглядность. Я тоже неоднократно использовал его в своих книгах. Он почти правилен, и все-таки не совсем, хотя недостаток весьма нетривиален. Следующий пример выявляет этот недостаток:</p>
<p><code>int main() {</code></p>
<p><code>const char* si = "GEW\334RZTRAMINER";</code></p>
<p><code>const char* s2 = "gew\374rztraminer";</code></p>
<p><code>printf("sl=%s,	 s2=%s\n",s1,s2);</code></p>
<p><code>printf("sl&lt;s2:2s\n",</code></p>
<p><code>	std: lexicographical_compare(s1,s1+14,s2,s2+14,lt_nocase())</code></p>
<p><code>		?"true":"false"):</code></p>
<p><code>}</code></p>
<p>Попробуйте запустить эту программу в своей системе. На моем компьютере (Silicon Graphics О2 с системой IRIX 6.5) результат выглядел так:</p>
<p><code>sl=GEWURZTRAMINER,s2=gewQrztraminer</code></p>
<p><code>sl&lt;s2:true</code></p>
<p>Странно... Разве при сравнении без учета регистра «GEWURZTRAMINER» и «gewurztraminer» не должны быть равными? И еще возможен вариант с небольшой модификацией: если перед командой printf вставить строку</p>
<p><code>setlocale(LC_ALL,"de");</code></p>
<p>результат неожиданно изменяется:</p>
<p><code>sl=GEW0RZTRAMINER,s2=gewurztraminer</code></p>
<p><code>sl&lt;s2:false</code></p>
<p>Задача сравнения строк без учета регистра сложнее, чем кажется сначала. Работа элементарной на первый взгляд программы в огромной степени зависит от того, о чем многие из нас предпочли бы забыть. Речь идет о локальном контексте.</p>
</section>
<section>
<title>
<p>Локальный контекст</p>
</title>
<p>Символьный тип char в действительности представляется самым обычным целым числом. Это число можно интерпретировать как символ, но такая интерпретация ни в коем случае не является универсальной. Что должно соответствовать конкретному числу — буква, знак препинания, непечатаемый управляющий символ?</p>
<p>На этот вопрос невозможно дать однозначный ответ. Более того, с точки зрения базовых языков С и С++ различия между этими категориями символов не так уж существенны и проявляются лишь в некоторых библиотечных функциях: например, функция isalpha проверяет, является ли символ буквой, а функция toupper переводит символы нижнего регистра в верхний регистр и оставляет без изменений буквы верхнего регистра и символы, не являющиеся буквами. Подобная классификация символов определяется особенностями культурной и лингвистической среды. В английском языке действуют одни правила, по которым буквенные символы отличаются от «не буквенных», в шведском — другие и т. д. Преобразование из нижнего регистра в верхний имеет один смысл в латинском алфавите, другой — в кириллице, и вообще не имеет смысла в иврите.</p>
<p>По умолчанию функции обработки символов работают с кодировкой, подходящей для простого английского текста. Символ '\374' не изменяется функцией toupper, поскольку он не считается буквой; в некоторых системах при выводе он имеет вид ü, но для библиотечной функции С, работающей с английским текстом, это несущественно. В кодировке ASCII нет символа ü. Команда</p>
<p><code>setlocale(LC_ALL,"de"):</code></p>
<p>сообщает библиотеке С о переходе на немецкие правила (по крайней мере в системе IRIX — имена локальных контекстов не стандартизованы). В немецком языке есть символ ü, поэтому функция toupper преобразует ü в Ü.</p>
<p>У любого нормального программиста этот факт вызывает обоснованное беспокойство. Оказывается, простая функция toupper, вызываемая с одним аргументом, зависит еще и от глобальной переменной — хуже того, от скрытой глобальной переменной. Это приводит к стандартной проблеме: на работу функции, использующей toupper, теоретически может повлиять любая другая функция во всей программе.</p>
<p>При использовании toupper для сравнения строк без учета регистра результат может быть катастрофическим. Предположим, у вас имеется алгоритм, получающий отсортированный, список (скажем, binary_search); все работает нормально, как вдруг новый локальный контекст на ходу изменяет порядок сортировки. Такой код не подходит для многократного использования. Более того, он вообще едва ли способен принести практическую пользу. Его нельзя применить в библиотеке — библиотеки используются множеством разных программ, не только теми, которые никогда не вызывают функцию setlocalе. Возможно, вам удастся применить его в какой-нибудь большой программе, но это приводит к проблемам сопровождения. Возможно, вам удастся проследить за тем, чтобы все остальные модули не вызывали setlocalе, но как предотвратить вызов setlocalе модулем, который появится только в следующей версии программы?</p>
<p>В языке С приемлемого решения этой проблемы не существует. Библиотека С использует единственный локальный контекст, и с этим ничего не поделаешь. Решение существует в языке С++.</p>
</section>
<section>
<title>
<p>Локальные контексты в С++</p>
</title>
<p>В стандартной библиотеке С++ локальный контекст не является глобальной структурой данных, запрятанной где-то в недрах реализации библиотеки. Это объект типа std::locale, который можно создать и передать его другой функции, как любой другой объект. Пример создания объекта для стандартного локального контекста:</p>
<p>std::locale L = std::locale::classic():</p>
<p>Локальный контекст немецкого языка создается командой</p>
<p>std::locale L("de");</p>
<p>Имена локальных контекстов, как и в библиотеке С, не стандартизованы. Список имен локальных контекстов, доступных в вашей реализации, следует искать в документации.</p>
<p>Локальные контексты С++ делятся на фасеты (facets), связанные с разными аспектами интернационализации. Для извлечения заданного фасета из объекта локального контекста используется функция std:: use_facet<a l:href="#n6" type="note">[6]</a>. Фасет ctype отвечает за классификацию символов, в том числе и преобразования типа. Если c1 и с2 относятся к типу char, следующий фрагмент сравнивает их без учета регистра по правилам локального контекста L.</p>
<p>const std::ctype&lt;char&gt;&amp; ct = std::use_facet&lt;std::ctype&lt;char&gt; &gt; (L);</p>
<p>bool result = ct-toupper(cl)&lt;ct.toupper(c2);</p>
<p>Предусмотрена особая сокращенная запись: std:: toupper (с, L), эквивалентная</p>
<p><code>std::use_facet&lt;std::ctype&lt;char&gt; &gt;(L).toupper(c)</code></p>
<p>Использование use_facet стоит свести к минимуму, поскольку оно связано с заметными затратами.</p>
<p>По аналогии с тем, как лексикографическое сравнение оказывается неподходящим в некоторых ситуациях, преобразования символов «один-в-один» тоже годятся не всегда (например, в немецком языке символ (3 нижнего регистра соответствует последовательности «SS» в верхнем регистре). К сожалению, средства преобразования регистра в стандартных библиотеках С и С++ работают только с отдельными символами. Если это ограничение вас не устраивает, решение со стандартными библиотеками отпадает.</p>
</section>
<section>
<title>
<p>Фасет collate</p>
</title>
<p>Если вы знакомы с локальными контекстами С++, возможно, вам уже пришел в голову другой способ сравнения строк. У фасета collate, предназначенного для инкапсуляции технических аспектов сортировки, имеется функция, по интерфейсу весьма близкая к библиотечной функции С strcmp. Существует даже специальное средство, упрощающее сравнение двух строк: для объекта локального контекста L строки х и у могут сравниваться простой записью L(x,y), что позволяет обойтись без хлопот, связанных с вызовом use_facet и функции collate.</p>
<p>«Классический» локальный контекст содержит фасет collate, который выполняет лексикографическое сравнение по аналогии с функцией operator&lt; контейнера string, но другие локальные контексты выполняют сравнение, руководствуясь своими критериями. Если в системе существует локальный контекст, обеспечивающий сравнение строк без учета регистра для интересующих вас языков, воспользуйтесь им. Возможно, этот локальный контекст даже не будет ограничиваться простым сравнением отдельных символов!</p>
<p>К сожалению, какой бы справедливой ни была эта рекомендация, она никак не поможет тем, у кого нет таких локальных контекстов. Возможно, когда-нибудь в будущем стандартное множество таких локальных контекстов будет стандартизировано, но сейчас никаких стандартов не существует. Если функция сравнения без учета регистра для вашей системы еще не написана, вам придется сделать это самостоятельно.</p>
</section>
<section>
<title>
<p>Сравнение строк без учета регистра</p>
</title>
<p>Фасет ctype позволяет относительно легко организовать сравнение строк без учета регистра на основе сравнения отдельных символов. Приведенная ниже версия не оптимальна, но по крайней мере она верна. В ней используется практически тот же принцип, что и прежде: строки сравниваются алгоритмом lexicographical_ compare, а отдельные символы сравниваются после приведения к верхнему регистру. Впрочем, на этот раз вместо глобальной переменной используется объект локального контекста. Кстати говоря, сравнение после приведения обоих символов к верхнему регистру не всегда дает тот же результат, что и после приведения к нижнему регистру. Например, во французском языке в символах верхнего регистра принято опускать диакритические знаки, вследствие чего вызов toupper во французском локальном контексте может приводить к потере информации: символы 'ё' и 'е' преобразуются в один символ верхнего регистра 'Е'. В этом случае при сравнении на базе функции toupper символы 'ё' и 'е' будут считаться одинаковыми, а при сравнении на базе tolower они будут считаться разными. Какой из ответов правилен? Вероятно, второй, но на самом деле все зависит от языка, национальных обычаев и специфики приложения.</p>
<p><code>struct lt_str_l </code></p>
<p><code>   :public std::binary_function&lt;std::string.std::string.bool&gt;{</code></p>
<p><code>   struct lt_char{</code></p>
<p><code>	const std::ctype&lt;char&gt;&amp; ct:</code></p>
<p><code>	lt_char(const std::ctype&lt;char&gt;&amp; c):ct(c) {}</code></p>
<p><code>	bool operator() (char x. char y) const { </code></p>
<p><code>	   return ct.toupper(x)&lt;ct.toupper(y);</code></p>
<p><code>	}</code></p>
<p><code>};</code></p>
<p><code>std::locale loc;</code></p>
<p><code>const std::ctype&lt;char&gt;&amp; ct;</code></p>
<p><code>lt_str_l(const std::locale&amp; L = std::locale::classic())</code></p>
<p><code>   :loc(L),ct(std::use_facet&lt;std::ctype&lt;char&gt; &gt;(loc)) {}</code></p>
<p><code>bool operator()(const std::string&amp; x, const std::string&amp; y) const {</code></p>
<p><code>return std::lexicographical_compare(x.begin(),.x.end(),</code></p>
<p><code>		y.begin(),y.end(), lt_char(ct));</code></p>
<p><code>}</code></p>
<p><code>};</code></p>
<p>Данное решение не оптимально; оно работает медленнее, чем могло бы работать. Проблема чисто техническая: функция toupper вызывается в цикле, а Стандарт С++ требует, чтобы эта функция была виртуальной. Некоторые оптимизаторы выводят вызов виртуальной функции из цикла, но чаще этого не происходит. Циклические вызовы виртуальных функций нежелательны.</p>
<p>В данном случае тривиального решения не существует. Возникает соблазнительная мысль — воспользоваться одной из функций объекта ctype:</p>
<p><code>const char* ctype&lt;char&gt;::toupper(char* f, char* i) const</code></p>
<p>Эта функция изменяет регистр символов в интервале [f,i]. К сожалению, для наших целей этот интерфейс не подходит. Чтобы воспользоваться этой функцией для сравнения двух строк, необходимо скопировать обе строки в буферы и затем преобразовать их содержимое к верхнему регистру. Но откуда возьмутся эти буферы? Они не могут быть массивами фиксированного размера (неизвестно, каким должен быть максимальный размер), а динамические массивы потребуют дорогостоящего выделения памяти.</p>
<p>Альтернативное решение заключается в однократном преобразовании каждого символа с кэшированием результата. Такое решение недостаточно универсально—в частности, при использовании 32-разрядных символов UCS-4 оно абсолютно неработоспособно. С другой стороны, при работе с типом char (8-разрядным в большинстве систем) идея хранения 256 байт дополнительных данных в объекте функции сравнения выглядит вполне реально.</p>
<p><code>struct lt_str_2:</code></p>
<p><code>   public std::binary_function&lt;std::string.std::string.bool&gt;{</code></p>
<p><code>   struct lt_char{</code></p>
<p><code>       const char* tab;</code></p>
<p><code>       lt_char(const char* t):tab(t) {}</code></p>
<p><code>       bool operator() (char x, char y) const {</code></p>
<p><code>	   return tab[x-CHAR_MIN] &lt; tab[y-CHAR-MIN];</code></p>
<p><code>     }</code></p>
<p><code>};</code></p>
<p><code>char tab[CHAR_MAX-CHAR_MIN+l];</code></p>
<p><code>lt_str_2(const std::locale&amp; L = std:.-locale::classic()){</code></p>
<p><code>	const std::ctype&lt;char&gt;&amp; ct = std::use_facet&lt;std::ctype&lt;char&gt; &gt;(L);</code></p>
<p><code>	for(int i = CHAR_MIN;i&lt;=CHAR_MAX;++i) tab[i-CHAR_MIN]=(char)i;</code></p>
<p><code>	ct.toupper(tab. tab+(CHAR_MAX-CHAR_MIN+1));</code></p>
<p><code>}</code></p>
<p><code>bool operator()(const std::string&amp; x. const std::string&amp; y) const {</code></p>
<p><code>	return std::lexicographical_compare(x.begin(),x.end(),</code></p>
<p><code>		y.begin(),y.end(), lt_char(tab));</code></p>
<p><code>}</code></p>
<p><code>}</code></p>
<p>Как видите, различия между lt_str_1 и lt_str_2 не так уж велики. В первом случае используется объект функции сравнения символов, использующий фасет ctype напрямую, а во втором случае — объект функции сравнения с таблицей заранее вычисленных преобразований символов к верхнему регистру. Второе решение уступает первому, если создать объект функции lt_str_2, воспользоваться им для сравнения нескольких коротких строк и затем уничтожить. С другой стороны, при обработке больших объемов данных lt_str_2 работает значительно быстрее lt_str_1. В моих тестах превосходство было более чем двукратным: при использовании lt_str_l сортировка списка из 23 791 слова заняла 0,86 секунды, а при использовании lt_str_2 понадобилось только 0,4 секунды.</p>
<p>Итак, что же мы узнали?</p>
<p>•Класс строки без учета регистра символов реализуется на неправильном уровне абстракции. Обобщенные алгоритмы стандартной библиотеки С++ параметризуются, и этот факт следует использовать.</p>
<p>•Лексикографическое сравнение строк осуществляется сравнением отдельных символов. Если у вас имеется объект функции, сравнивающий символы без учета регистра, задача фактически решена, а этот объект может использоваться для сравнения других типов последовательностей символов, таких как vector&lt;char&gt;, строковые таблицы или обычные строки С.</p>
<p>•Задача сравнения строк без учета регистра сложнее, чем кажется на первый взгляд. Она имеет смысл лишь в конкретном локальном контексте, поэтому объект функции сравнения должен содержать информацию о текущем локальном контексте. Если сравнение должно быть оптимизировано по скорости, напишите объект функции таким образом, чтобы избежать многократного вызова дорогостоящих операций с фасетами.</p>
<p>Правильное сравнение строк без учета символов требует большого объема рутинной работы, но ее необходимо проделать только один раз. Или вам, как и большинству коллег, не хочется думать о локальных контекстах? Впрочем, лет десять назад никому не хотелось думать об «ошибке 2000 года». И все же у вас больше шансов обойти стороной эту проблему, если ваш локально-зависимый код будет с самого начала правильно работать.</p>
</section>
</section>
<section>
<title>
<p>Замечания по поводу платформ STL от Microsoft</p>
</title>
<section>
<p>В начале книги я ввел термин «платформа STL», означающий комбинацию конкретного компилятора и конкретной реализации STL. Различие между компилятором и библиотекой особенно важно при использовании компилятора Microsoft Visual С++ версий 6 и ниже (то есть компилятора, входившего в комплект поставки Microsoft Visual Studio версий 6 и ниже), поскольку компилятор иногда способен на большее, чем прилагаемая реализация STL. В настоящем приложении описаны важные недостатки старых платформ STL от Microsoft и предложены обходные решения, делающие работу на этих платформах значительно более удобной.</p>
<p>Дальнейший материал предназначен для разработчиков, использующих Microsoft Visual С++ (MSVC) версий 4-6. В Visual С++ .NET перечисленные проблемы отсутствуют.</p>
</section>
<section>
<title>
<p>Шаблоны функций классов в STL</p>
</title>
<p>Допустим, у вас есть два вектора объектов Widget, требуется скопировать объекты Widget из одного вектора в конец другого. Задача решается легко — достаточно воспользоваться интервальной функцией insert контейнера vector:</p>
<p><code>vector&lt;Widget&gt; vw1,vw2;</code></p>
<p><code>vwl.insert(vw1.end(),vw2.begin().vw2.end()); // Присоединить к vw1 копию</code></p>
<p><code>// объектов Widget из vw2</code></p>
<p>Аналогичную операцию можно выполнить с контейнерами vector и deque:</p>
<p><code>vector&lt;Widget&gt; vw;</code></p>
<p><code>deque&lt;Widget&gt; dw:</code></p>
<p><code>vw.insert(vw.end(),dw.begin(),dw.end()); // Присоединить к vw копию</code></p>
<p><code>// объектов Widget из dw</code></p>
<p>Оказывается, эту операцию можно выполнить независимо от того, в каких контейнерах хранятся копируемые объекты. Подходят даже нестандартные контейнеры:</p>
<p><code>vector&lt;Widget&gt; vw;</code></p>
<p><code>list&lt;Widget&gt; lw;</code></p>
<p><code>vw.insert(vw.begin().lw.begin().ww.end()); // Присоединить к vw копию</code></p>
<p><code>// объектов Widget из lw</code></p>
<p><code>set&lt;Widget&gt; sw;</code></p>
<p><code>vw.insert(vw.begin(),sw.begin(),sw.end()); // Присоединить к vw копию</code></p>
<p><code>// объектов Widget из sw</code></p>
<p><code>template&lt;typename T,</code></p>
<p><code>typename Allocator - allocator&lt;T&gt; &gt; // Шаблон нестандартного</code></p>
<p><code>class SpecialContainer {...}:// STL-совместимого контейнера</code></p>
<p><code>SpecialContainer&lt;Widget&gt; sew;</code></p>
<p><code>vw.insert(vw.begin().scw.begin().scw.end()); // Присоединить к vw копию</code></p>
<p><code>// объектов Widget из scw</code></p>
<p>Подобная универсальность объясняется тем, что интервальная функция insert контейнера range вообще не является функцией в общепринятом смысле. Это шаблон функции контейнера, специализация которого с <emphasis><strong>произвольным типом итератора </strong></emphasis>порождает конкретную интервальную функцию insert. Для контейнера vector шаблон insert объявлен в Стандарте следующим образом:</p>
<p><code>template &lt;class Т, class Allocator = allocator&lt;T&gt; &gt;</code></p>
<p><code>class vector {</code></p>
<p><code>	public:</code></p>
<p><code>	template&lt;class InputIterator&gt;</code></p>
<p><code>	void insert(iterator position, InputIterator first. InputIterator last);</code></p>
<p><code>};</code></p>
<p>Каждый стандартный контейнер должен поддерживать шаблонную версию интервальной функции insert. Аналогичные шаблоны также обязательны для интервальных конструкторов и для интервальной формы assign (см. совет 5).</p>
</section>
<section>
<title>
<p>MSVC версий 4-6</p>
</title>
<p>К сожалению, в реализации STL, входящей в комплект поставки версий 4-6, шаблоны функций не объявляются. Библиотека изначально разрабатывалась для MSVC версии 4, а этот компилятор, как и большинство компиляторов того времени, не обладал поддержкой шаблонов функций классов. При переходе от MSCV4 к MSVC6 поддержка этих шаблонов была включена в компилятор, но вследствие судебных дел, косвенно затрагивавших фирму Microsoft, библиотека оставалась практически в неизменном состоянии.</p>
<p>Поскольку реализация STL, поставляемая с MSVC4-6, предназначалась для компилятора без поддержки шаблонов функций классов, авторы библиотеки имитировали эти шаблоны и заменили их конкретными функциями, которым при вызове передавались итераторы контейнера соответствующего типа. Например, шаблон insert был заменен следующей функцией:</p>
<p><code>void insert(iterator position,// "iterator" - тип итератора</code></p>
<p><code>	iterator first, iterator last): // для конкретного контейнера</code></p>
<p>Эта ограниченная форма интервальных функций позволяла выполнить интервальную вставку из vector&lt;Widget&gt; в vector&lt;Widget&gt; или из list&lt;int&gt; в list&lt;int&gt;, но смешанные операции (например, вставка из vector&lt;Widget&gt; в list&lt;Widget&gt; или из set<emphasis><strong>&lt;int&gt;</strong></emphasis> в deque&lt;int&gt;) не поддерживались. Более того, не поддерживалась даже интервальная вставка (а также конструирование или assign) из vector&lt;long&gt; в vector&lt;int&gt;, поскольку итераторы vector&lt;long&gt;:: iterator и vector&lt;int&gt;:: iterator относятся к разным типам. В результате следующий фрагмент, принимаемый другими компиляторами, не компилируется в MSVC4-6:</p>
<p><code>istream_iterator&lt;Widget&gt; begin(cin),end;</code></p>
<p><code>vector&lt;Widget&gt; vw(begin.end);</code></p>
<p><code>list&lt;Widget&gt; lw;</code></p>
<p><code>lw.assign(vw.rbegin(),vw.rend());// Присвоить lw содержимое vw</code></p>
<p><code>// (в обратном порядке);</code></p>
<p><code>// не компилируется в MSVC4-6!</code></p>
<p><code>SpeciаlContainer&lt;Widget&gt; scw:</code></p>
<p><code>scw.insert(scw.end(),lw.begin(),lw.end()); // Вставить в конец sew</code></p>
<p><code>// копию объектов Widget из lw: </code></p>
<p><code>// не компилируется в MSVC4-6!</code></p>
<p>Так что же делать, если вы работаете в среде MSVC4-6? Это зависит от используемой версии MSVC и того, вынуждены ли вы использовать реализацию STL, поставляемую вместе с компилятором.</p>
</section>
<section>
<title>
<p>Обходное решение для MSVC4-5</p>
</title>
<p>Еще раз посмотрим на правильный код, который не компилируется для реализации STL из поставки MSVC4-6:</p>
<p><code>vector&lt;Widget&gt; vw(begin,end);// Отвергается реализацией STL</code></p>
<p><code>// из поставки MSVC4-6</code></p>
<p><code>list&lt;Widget&gt; lw;</code></p>
<p><code>lw.assign(vw.rbegin(),vw.rend());// То же</code></p>
<p><code>SpeciаlContainer&lt;Widget&gt; scw;</code></p>
<p><code>scw.insert(scw.end(),lw.begin(),lw.end()); // То же</code></p>
<p><code>// Создать итераторы begin и end </code></p>
<p><code>// для чтения объектов Widget </code></p>
<p><code>// из cn (см. совет 6).</code></p>
<p><code>// Прочитать объекты Widget </code></p>
<p><code>// из cin в vw (см. совет 6)</code></p>
<p><code>// не компилируется в MSVC4-6!</code></p>
<p>Несмотря на внешние различия, выделенные вызовы отвергаются компилятором по одной и той же причине: из-за отсутствия шаблонов функций класса в реализации STL. Соответственно и решение во всех случаях оказывается одним и тем же: замена вызовом сору с итератором вставки (см. совет 30). Ниже приведены обходные решения для всех примеров, приведенных ранее:</p>
<p><code>istream_iterator&lt;Widget&gt; begin(cin).end:</code></p>
<p><code>vector&lt;Widget&gt; vw(begin,end);	//Создать vw конструктором</code></p>
<p><code>copy(begin,end,back_inserter(vw));//по умолчанию и скопировать</code></p>
<p><code>//в него объекты Widget из cin</code></p>
<p><code>list&lt;Widget&gt; lw;</code></p>
<p><code>lw.clear();	//Удалить из lw старые объекты:</code></p>
<p><code>copy(vw.rbegin(),vw.rend(),//скопировать объекты из vw</code></p>
<p><code>back_inserter(lw))://(в обратном порядке)</code></p>
<p><code>SpecialContainer&lt;Widget&gt; scw;</code></p>
<p><code>copy(lw.begin().lw.end().// Скопировать объекты Widget</code></p>
<p><code>inserter(scw.scw.end()));// из lw в конец sew</code></p>
<p>Я рекомендую использовать эти обходные решения с библиотекой, входящей в комплект поставки MSVC4-5. С другой стороны, будьте внимательны и не забывайте о том, что эти решения являются <emphasis><strong>обходными. </strong></emphasis>Как показано в совете 5, алгоритм сору почти всегда уступает интервальной функции контейнера, поэтому как только представится возможность обновить платформу STL до версии с поддержкой шаблонов функций класса, откажитесь от использования сору в тех местах, где следовало бы использовать интервальные функции.</p>
</section>
<section>
<title>
<p>Обходное решение для MSVC6</p>
</title>
<p>Обходное решение из предыдущего раздела подходит и для MSVC6, но в этом случае существует и другой вариант. Компиляторы MSVC4-5 не обладают полноценной поддержкой шаблонов функций класса, поэтому отсутствие этих шаблонов в реализации STL несущественно. В MSVC6 дело обстоит иначе, поскольку компилятор этой среды поддерживает шаблоны функций класса. Таким образом, возникает естественное желание заменить реализацию STL из поставки MSVC6 другой реализацией с шаблонами функций классов, предписанными Стандартом.</p>
<p>В совете 50 упоминаются свободно распространяемые реализации STL от SGI и STLport; в списках поддерживаемых компиляторов обеих реализаций упоминается MSVC6. Кроме того, можно приобрести новейшую MSVC-совместимую реализацию STL от Dinkumware. У каждого из этих вариантов есть свои достоинства и недостатки.</p>
<p>Реализации SGI и STLport распространяются бесплатно, поэтому какая-либо официальная поддержка в этих случаях попросту отсутствует. Более того, поскольку реализации SGI и STLport рассчитаны на работу с разными компиляторами, вам придется дополнительно настроить их для обеспечения максимального быстродействия в MSVC6. В частности, может потребоваться включение поддержки шаблонов функций классов — из-за совместимости с большим количеством разных компиляторов в SGI и/или STLport эта поддержка отключена по умолчанию. Возможно, также придется позаботиться о компоновке с другими библиотеками MSVC6 (особенно DLL), проследить за использованием соответствующих версий для отладки и т. д.</p>
<p>Если подобные вещи вас пугают или вы руководствуетесь принципом «бесплатные программы обходятся слишком дорого», рассмотрите альтернативную реализацию STL для MSVC6 от Dinkumware. Библиотека проектировалась с расчетом на максимальную простоту замены и на соответствие Стандарту. Реализация STL из MSVC6 разрабатывалась именно в Dinkumware, поэтому вполне возможно, что новая реализация STL <emphasis><strong>действительно </strong></emphasis>легко заменяет оригинал. За дополнительной информацией о реализациях STL от Dunkumware обращайтесь на сайт компании <a l:href="http://www.dunkumware.com/">http://www.dunkumware.com</a>.</p>
<p>Независимо от того, на какой реализации вы остановите свой выбор, вы получите нечто большее, чем STL с шаблонами функций классов. В альтернативных реализациях будут решены проблемы соответствия Стандарту в других областях — скажем, отсутствие объявления push_back в контейнере string. Более того, в вашем распоряжении окажутся полезные расширения STL, в том числе хэшированные контейнеры (см. совет 25) и односвязные списки (контейнер slist). Реализации SGI и STLport также содержат множество нестандартных классов функторов, включая select1st и select2nd (см. совет 50).</p>
<p>Но даже если вы вынуждены работать с реализацией STL из поставки MSVC6, сайт Dunkumware все же стоит посетить. На нем перечислены известные ошибки в реализации библиотеки MSVC6 и приведены рекомендации относительно того, как модифицировать библиотеку для сокращения ее недостатков. Не стоит и говорить, что редактирование заголовочных файлов библиотеки — дело весьма рискованное. Если у вас возникнут проблемы, не вините в них меня.</p>
<empty-line/>
</section>
</section>
</body>
<body name="notes">
<title>
<p>Примечания</p>
</title>
<section id="n1">
<title>
<p>1</p>
</title>
<p>В среде программистов данный термин (англ. mutex) встречается также в варианте «мутекс». — Примеч. ред.</p>
</section>
<section id="n2">
<title>
<p>2</p>
</title>
<p> Строго говоря, не все 24 перестановки равновероятны, так что вероятность 1/24 не совсем точна. Тем не менее, остается бесспорный факт: существуют 24 разные перестановки, и вы можете получить любую из них.</p>
</section>
<section id="n3">
<title>
<p>3</p>
</title>
<p> Термин употребляется в статистике. — Примеч. ред.</p>
</section>
<section id="n4">
<title>
<p>4</p>
</title>
<p> Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. «Приемы объектно-ориентированного проектирования. Паттерны проектирования»  — СПб.: Питер, 2001.</p>
</section>
<section id="n5">
<title>
<p>5</p>
</title>
<p> См. статью Александреску A. (Andrei Alexandrescu) в майском номере «С++ Report» за 2000 г. [19].</p>
</section>
<section id="n6">
<title>
<p>6</p>
</title>
<p> Внимание: в шаблоне функции use_facet параметр шаблона присутствует только в типе возвращаемого значения, но не в аргументах. При обращении к нему используется языковое средство, называ-емоеявяьш заданием аргументов шаблона, которое не поддерживается некоторыми компиляторами С++. Если ваш компилятор принадлежит к их числу, возможно, авторы реализации библиотеки предусмотрели обходное решение, которое позволяет использовать use_facet другим способом.</p>
</section>
</body>
<binary id="img_0.jpeg" content-type="image/jpeg">
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
yis8PlFZ27ylnbvKWeeMZWeM3Cs9buFZ63cKOrlTfL81k/eX+YUvc/VQd4rKf8f14TsT+8fj03y/
NRPkNGg4lFkczXOtC4FSOleGCxpUQila8hxrQrKP8f14TsT+8fj0wAqafquqfurqn7q6p+6uqk3V
PbY5uGI4TsT+8fUtrG3AaFIBhaTA1tpxdcNaytks7ZDxZNhuDSEwsuc6U307Av8AkHtpXm6O1ZRl
JoZI2gNu1nFcZJe9kjQDpoU7J6DinSWCzQo24NbI+07suWTyxAR1jwA0VKjjJ5ltl1Ni/qKARUL3
DtGhZdao0GJt9MME2UPD43MeK000+DyPY4h1142qJj5nuaa1BPZyyn94+pA5t3Yi44m9Ne25zTUJ
9iKNtsUfdimMoA1mACmJp6WlpGgBa4UIOlMjawMjDrVBrUskbIy6psyaaI5PXmF1SmNPuCgQn98E
HwT4q817rRUjy1ptto4EJtkMaGggAC6/4PPs/VMmaL2611DPFdQzxXUsUgexrbNMOF2xP2/Hpvl+
ajhLqWtKzl26s4durOHbqeRIXWuzhdsTtvI577Pyqs6/0KzweWVnn0ys7+mVnf0ys7PlrO3eUs7d
5X8rO3+V/Kzt/lfys7d5X8rO3eV/Kzt/lfys6f5X8rOn+X/Kzpw2xrO/plZ2fLWdu8pZ27yv5Wdv
8r+Vnb/K/lZ27yv5Wdu8r+Vnb/K/lZ0/yv5WdP8AK/lZy/y/5WcSeX/KziXy/wCVdPJ5f8rr5PL/
AJWdu8r+Vnb/ACv5Wdu8r+VnbvK/lZ27yv5Wdv8AK/lZ0/yv5Wcv8tZzJ5f8rOJfL/lZxL5f8rr5
fL/lXTS+WP3WcvH/AOr+VnbvK/lZ27yv5Wdu8r+VnT/K/lZ0/wAr+VdhypI4xV5pd81FNNHZjaby
XBU46vdFVTjabQqxvDh2HkO2J23486R9zWipRqSGaGr0cL3bAquyeQDuq3G4tcFaPTFzhwu2J20+
sleD1bbR9XKQR6Nls+zMiGLnBqc3UaculoN2qlppPYfUUB6TwE1r+g0WiFQCg4G5Q0APtUPapWe6
WV/HhdsTtp9SPRNw1KQDC0oo3YOeAsuDaMawc1oGHOWSTyNaOOtMk/Kqka8c90lgbBj+iIrFZqeZ
8lFK4AlhcbGl2CyiQtFrjGYDDFf8bzRznGu8n2mC3Kx5aD7rQCsmc65gDnSHsqo3BgaDE00CjYcH
OAWXxsAY2OMhoA1FZJlb2ipcWyDWP/pWVVAdzgyM/jXwTeezFvModSDnC0WyPIj0vuCY17WESOvq
wKed7WVhZzRZAvqpXSUL4i2jtN6MLY2cVaDXNs4qRhexhblBaC4agsptWTzGOaRqJWUc2p4o08Qp
oaC2wttu7b7lO17g0cX0jo5wUMIjstY8NDTjjpWV1NQHtpd2lMhmfG1pbTiBfoTHxXPleau7ApJ3
RgPZDaF2JwqmCRtv0g5x0diygRStLwXGxSibQfZM/wD5CyGKNo59l7nUvNVlT3htuFwc007U/Kg0
cTxfGgaKm6nisjcXsYS01rpvWWxktY3mC1TDBPZxdhrIngNIw5pWUGnvN/VNyhzRZaxgaPvOshZe
1jAXcaA3xKY2MDmSFtv713qH0xZz02WldBHYrp2jsdcqnKI/FNiirYBqSdKln0UsjhdsTtvqQLLL
kXnSaqoxWUmWOMvc0axavTAbLQBZaMAFG0ODhGwCo0nSjlNBbJqog27iySDtUrTBGWyOqRemERNb
JC8GOlfn+idOaFzgR4iibk32bTVQvmAssaG4aFULKXTRsL3R44WrwmN5rWtua0YBZPCHB3FsvI0l
f1N1tMYDSw60CNf/ANCGUWW2q1poRcLwRRwOkLiY4wxhNTpqnyCJj3Douqix8bJAX2+drTwQ0BzQ
2gGACL46VLbN6kZoeQSpAMHtsnxTHUaXtbQOIvT49DiCfkuOETOO0u1oxuja+Mmtk6CuNAaObZs0
upqQbHGyNodaoNKe5sUTJH1tPA1q0fugeAosnbMGlrHiy4+7enxxsDI3OqaaVHk9ppcXFxsnAalG
w4RigU1aelpaTZCGlzW2bxjtUkb4YwxzT0QcaXKMPN0YstU5FKz9I/shFotWvUmTJb2n3NSo+NwP
aFRrCdgQMg4pmspscYo0cLtidt+PGSQ0aNK6/wDArrv9Suu/1K678CiYX2qY8LtiO3kG3M2PaFns
e6Vnse6Vn0PgVn8XgVnsXgVnkXg79lnsXg79lnsW679lnsW679lnke679lnkXgVnsXgVnse6Vnse
6Vnse6VnrN0rP4t0rP490rP49wrP2bjln7PLcrstj3XLPIvB37LPIvArPY90rPY90rPY90rPY90r
PGbhWfR7hWfx7hWfs3HLPmbjldlse6VnkXgVnkXgVnsW6Vnse6Vnse6Vnse6VnjNwrPG7hWet3Cs
+ZuFZ+zccs+ZuFZ8zcKz2LwKzyPwKz2PdKz2PdKxrypGsBJqMNvBYjbV2pZtJ4LNn+CmEsZZUile
F2xHb8frSxJ95qtEVboe1Bs3pGfiFWJ9ezTyHbEdvwR7i8MjYKucVxsUwfHao40oQnS/1zLANCbB
TpWvDmB9iuv2d0wbapS5Zu3xVDk7abVbZA2LWAUHMcWuGkKTjaEspfwu2I7fUtrE3AaFIBcLShac
C8LKIjZsASUFkaismdGAHuLjbpfimzljc240t0WllHG0JYLbTTC9PiFkMoLrI1LJ2RgNHFNtCnS2
rKhCGMYCPSO93sC/49/NeX9I0uN6/qbI4mzxtNFdXisuD2NsmgF3RqpuNYC9wc1tRhQXlZK91zGs
c6Q/5Jz+KZT+mtWaXYKcS3mOjmmmF9KJuTsaOLbQEferisqax8bLMwaHPFbr1G6OKN54mtv3T/cu
MfIyR7ZaWm6qKLnRgc3mU7EA0AemdztTaLJLLGtjsOu10ripRPlEb7vR2RgfBS9G6MkFwqAo3cx7
rd72Cg2KW1FxkDqB4/JSTZNbaY6W2ONVlH/a39U//vH5ezugtWa6VnP+izn/AEWc/wCqzn/VSDjL
dumjhdsR2+pAoy7sRecSaprxi01T5aCr7VfmsljLWyUtVbqvQmoLhZs6Kak6KOJsYeedfUovdiVE
bEctmNtDqNFIJmiQSOtGt16gdYaOJ6KMF1m1aU9aelpVB5AqGFnjpTMmusNv2pz32RSAsHbcjFHG
2NrjV1NKY98LHSMFziT8lI17GyW3Wja1ocxtgMsWOxGBsQbGTX5puU0FsU/BPgFKOdUlQltAYq0T
2xwsZbxITjZDqtLSCuJbG1jK2rtJTm2Wvjd0muwKMMcTImG82dKdCOi4glOhMTJGF1rnIUhZH3fZ
pJmgEt0FdTGupjXUx/iupj/FS22tFimHC7Yjt+PT7P1UcJNA7Ss4f4Lr3+Cuyg7qkHGW7dNHCdiO
3kHjJbHyqs7+mVnZ8srPPplZ63cKzxu4Vng8srPPplZ59MrPPplZ59MrPPplZ59MrPPplZ59MrOz
5az36ZWe/TKz0+UVnn0ys7+mVnn0ys8+mVnn0ys8+mVnn0ys8+mVnh8srOz5azt3lLO3eWs8d5Sz
x3lLPD5RWefTKzz6ZWefTKzz6ZWefTKzw+WVnZ8tZ27ylnbvK/lZ0/yv5WdP8r+VnjvK/lZ47yld
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
APAENOKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKBEHKAAAAAANFPQPdQAAAAAAAAAAAAAAAAAALFA
NPIAAAAAMgiqJivkkkoFNDOrEvLAAAAAAKGGPKKAAAAAD167l6v8ygyzFLovxlvQAAAAAODFFKIA
AAAANXCSeSTDgTDHDbPDGTQAAAAAAPLPPOKAAAAFrDt4IkbcbaVbqbUjEyJ3AAAAALAJFKKAAAAB
hlF0w4vlusli0kVDiFVFAAAAALBGHPCAAAAPXdTNbSVfbsXbbYfBHZXfgAAAALAJNPAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAOAAPPKAAAAAAABEQIZUcMcccMUCAAAAAAAAAODHHKCAAAAAAAGAAiAq
wgggCQkQAAAAAAAAAKCBHKKAAAAAAAAQFSQMXQggIAQQAAAAAAAAAPEJNPDAAAAAAAHQAMSALAgA
CQxQAAAAAAAAALGGHLCAAAAAAAALAgECFAggKQgQAAAAAAAAAKCMNPKAAAAAAAMMdQQIFgAAKQgQ
AAAAAAAAAOPLHPKAAAAAAAFQBaQEQAAgKQh0QAAAAAAAAPNPNOAAAAAAAAFQAAAqQAggCQAgGAAA
AAAAAKAIFLKAAAAAAAAKDRUEcAUUZMQEcAAAAAAAAOPPNHKAKIwlksgCgAMIAQQQYMAAAQAQQQAA
ALKNHPKAAABAAAAAAAAAAAAAAAEAAAAAAAAAAKBOPMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKKA
FCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPEAFPKADDDBDBDDDDDDADDBDDBBCBDDBDAAAOBHNKKA
IAMELPCDEBAEIAEEAAAAEIAAAAAAAPPKHKKAAACSCPOONEDAAAAAAAAAAAAAAAAAAKMDNLKACACA
DAABDBCBDBACCACADADDCBAAAKLDHHIAAAAACDBBAAAAAAAAAAAAAAAAAAAAALMJPPKAAAJXLPFL
KKAAAAAAAAAAAAAAAAAAAPAMPPKABDDCCBDDDDBACDBCDDDDBCCDDCAAAODAFPKAAAAAACDBCCAA
AAAAAAAAAAAAAAAAAOJMNPKAAACUKJGGNHAAAAAAAAAAAAAAAAAAAKIFNPKABDCCCDDACCADDAAB
BDDACADCDACAAKEKFPKAAAAACAAAABAAAAAAADCABAAABAAAAPPFNPKAAALwJMDBHENHAKCBDLCA
AEDCBAAAALKDPPIADAACDDADADADCADDBCDBDDCDDBAAAKMANNKAAAAABABAAABAAABABBAAAAAA
AAAAAKFEPFKAAAADMOHLOPCHLMLLBPCAAAAAAAAAAKNJHHCAABDACBBDDBBDACDDDDDCBADDDBAA
APMOFPAAAAAAAAAAAACABAAAAAAAAAAAAAAAALBPPFAAAAABGOPEBLGAMCCAAAAAAAAAAAAAAPCK
FFBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALKDPFGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOPFNPKA
AAACAAAAAAAAAAAAAAAAAAAAAAAAAOPOFNDAAADFAAAAAAAAAAAAAACACBCDACAAAPECPFHADDOA
CBCAAAAAAAAAAAIKFONOGEIAAOLAHHKEEMIAEECAAAAAAAAAAAIIAAAAAAAAAPPIINKAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAP/EACURAAECBQMEAwAAAAAAAAAAAAABERAgITFBQFBgMGFwwVGAsf/a
AAgBAwEBPxDw24EipIDqIIdNhECCpACAAEFgEpAAAAkkACQOCiACoaJXg6AAAAADARLYAA9RBDJE
PAAs6KwBBSA2AQoNcCgxgYugt4ZDslgdouAhYYDSkAAOEwAQAQQEgoZEAAAUyoAhAKhohAewiwP1
TgAgLJREA+0AV+HIAGAYaEeEEVAqoxhZigEcjsQipiqEFAYQUHGGMREgcgiNvaAFAiAdwMCAAAAA
AAcAoPG4CgABCAIAFgAAAAAAEAAgAAAAubEAALBBhEAG3tQQAgoAgAAAAfxokizlafyyJIs5Wl8s
jGLPsF//xAAhEQABBAEDBQAAAAAAAAAAAAARAAEQIDFAUHBBUWCAkP/aAAgBAgEBPxDmZAAAAAAA
AAAB02EAAcagHUcgaig6wABAHgTuXAAA0gHhk2IAAAoHQJFQEjQKQ/L8AAAAAAAAAAAAAAADWXLA
d5YA9g//xAAsEAACAQMCBAYCAwEBAAAAAAAAAREQITEgQTBAUWFQYHGBofCRsdHh8cFw/9oACAEB
AAE/EP8AxLBgwYMGDBgwcAEEABiTIDwQg8GRABigAKCOdLiBIBIVAUQ6A6IfgvwHy9AAfPguh8AB
YdKgwEGUgoAfPBWQQA8baSB1AcdCABbqciHyhfIASYiJklQAUZzc1fVUAAjEAFY5cAJWjgCHW5IA
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
</binary>
<binary id="img_1.png" content-type="image/png">
iVBORw0KGgoAAAANSUhEUgAABM4AAAGdAQMAAADQZa3oAAAABlBMVEX///8AAABVwtN+AAAYcklE
QVR4nO3dz4/jSl4A8PJ4NPWQnrre3kai1zX8uHCbvQXwxo/T3tgrNwY4cO0nDvixeW2HRmoQq81h
r2hb4j9A4rCHp30VRSIIEH1GWmkq5NAXpK6oEV3DePylftiOy07SPZmednbxd1e7PXHifFK/yz/K
CPXRRx999NFHH3300UcfffTRRx8fHp983rVga1DoWrA1KPCuCdvioGmia8K2CNAB02TXhG2haJh/
9dc+L19Ii/9/0YXGCUUjEnIqSe6DiDIsMQuO0JGXdy3TZY1mkCcZBUVLMpIRFhFE/O7bu8ATSa5o
OYUzEJDRnPCIIoIh7Zqm2rXTIWbZkJIztspGEVW0ABEyZwdAiwPMZBDgs3QlwhGZ8ETR6LpidEcT
8gizE0vj4QgXNO8AaFIgn6FhQM51qsV4IkCVtaB7WuBpWkpUNVA0GSagaRk5gL41QLGmJUABp6qG
KppUfx8Y7aygvVF/Tw6E9hSnEFCYp6o3SG7Jb19TuKTv2CHQCE5142FoMSLRZeBfBl3DDE1SPBZB
4CvaqW48DO0AGg9VQ0eBog0NLdNNbjTRtINock9PVYYmqnylQoZDOlH/wZfk/BBoaiSUgqqVoJvc
SLUbqnpeEmDd03hJ+0bTVJN2oRq1CT4EWkqkbdcidMNDkhOiaGq8lnZNU+EzHwVP0Isn6FX63Bf4
6BnJjlDWNauPPvroo48++uijjz5+mWPgnUkMU+axriWtCP15RmDBD5GGL3MCS+6zriWtGOILTTvE
DA3wBSXeYdJIcLg0pGmHWA0IRbmiwZh1LWkFCTxDW6RdS1pBfmJpN6xrSSvwTxBo2hvWtaQVOEOR
rqE/YF1LWoGzY3KgNF8ODe1L1rWkFb6klKp27S3rWtIKT1JIVDU4RJqwtANsPDxB8lNvBgvWtaQV
nsBy5E0PsaPq/pzU1jhgGmVdC7bGIdPSrgVbg3YN2B4HcEa7jz766KOPPvroo48++uijy4Bm3PNz
XvoxVSZatPWdSH5rG6+2Rfv+pA+hra94bNPW93YlO2kvD4uWbt/l+mNeqtSf3JPGnX89RShc0672
onmtTesygncm9k4acmhpc9sH08iOxH4o2unT38TPsAS4bNP+05PfuQqSi4tJbra820Vj70E7vh+t
uYmsaa1vG6zfVXtVf+AFfh/aPVNtP9pRc9OO07zt47G7aCcPT2t9R1c00tz0YLQdGbo+evpQtIcr
a+tvey9a683rTY9Lu3c1CB6SVr35QWhqdw6N12jhdpre3ehj0/h2WuTS7N8Fjbu05zXapw9Fyxya
59DYVpq+U6ROM58oaihpfJv5O7Z/8/egYdhOowLhGg3XaeCWNfOJItUIc77N/l00h8KlPavR/DYt
rWgv9PnNOi3bQWMOzeRXceqFiO20U7fxOKrTap8paMLSfkN4OU6/kNUXmaUxKpr6f9sNF7REOBka
12g4205LUifVHFpzbQQfMktLMh8wswPcgkaAW9qEYRl4c16n5RVNj1jN36T8tRtoxXg84VtpXvNG
JzXCHhc0DOfTW5cmTdH3QJCM+naAW9IgNZxPhygZi2+DQ1tfzvZ5mVFFqkVyO605pFS0laXlZD5b
Xrk0KGk0p/i1UfHiO0AY2hEgmMrEpUn7bd/jfkbGt7JOg4r2BBVn38rfsZ6s/fAYvTK0d+alUUD9
xc2iXg0UbW5pMqJkclFPtQgyQ6MwBi6/Mr+YVr/WfhtInJOzBo0Z2pMIUT60t/hVtHIkXJsrWRql
Hr+dmr8j//yfvsVjUoypPRGPyPnlOXNotljSq7OVyKRJtaJdU3ueFjQCeHZVp1GQRzZlWSSGxKWV
a0fgBi3WN60X5S5ypxSeGI3w2WtbJQDyHF79LxQ0MpszOZJFCpaxsrSMTs7nc5eWl7REUvKNQysn
Cc1Ui/Wd4clmGg9jfJaXtDIs7WzO4kjELs1+iQyHeHZ9xhwa/MjSeKbyKXJptclqIVzTIm8LLYHx
Fpo/T2PKTcUhTdpw6E/B0r5SL15kp2rPJtvwdKHy82duNQC3+ahqqKENnY5KfdVtRUuzbTSQlA02
pZoIQkWzjUdSbSpoKxHg3NLg9hJu8t+Cxoy0atdOCH3HkiZttqZJp6OK6jTCNmaoCFSFzDfT0hUP
cFbQ3KJVBoa86EmIel9R1qp2LbftGguTWy9r0PJRi3bxuviKt2vaqaV9eTvTrGhNUzuXd9BkSaNV
DW32BiyMkVeUNV7SZEU7obzeh1aNR522rgb/XtE8WZa1X/nR38BfNDNU9yq8oPmLol2runeGC9qo
qqEFLQFW0lIUNWn21/IgepvmDVrReLg0EwDuojjVeO2EkPPlkguHhioaOX/tZKjqmkrauvEod5mV
tFDlVoMmLA2t4OdlhqYlzR17ELmmwWwpTNlujNcUbUgnE9sKlDS5psmviia3pPGSNqxqaEVjJY2j
shpUNObSyrlBTDDMp6LevUeiokVAJrN6H6pKbEGbLaTMTh0aq2jNJrfs3hVNVDW0EDXycz2jiokP
EybzOi218wHVeFCg2KWlFQ1WcpTUaKQcr/GAkvl8xuu0bE1rNLle7RiciaNyPnByhDKSynqqnaKK
RvLIX5qPcrstL4ePKstW4jRKazS5psFEJWqdxkuakKeUUofGUTPaU+SC9lLvwnx24Iuhv6yN1/Qc
oqAxUH1OnUbLaYsICFycreq0cgCuaJBFZKLfV5Q1b8OB0600VNCcKGisoD3zU9XxBkZV0lBFw0A9
UaedFiNbnIokOyWXNZq/YaWBXfPZbTQdrdl7QTsud8mPVcvliUGNJta0SI7wvE7jqBU7aen70zbu
sqCxkoa46t3wtEZrf9UdGfrANLT9qOSTtuxQaJviF5XWevfj0lr76Wn3oPEZg9TbTGt97HFp0Iz6
abi3atA8+OEX1bKMa5oa7t9cwbPfSctNj01rhkNrxC8Czc8gB/iXfxyIXycv0jto7UPZa1r1baqM
ffa3Pwb84bTWpvXY2k6kbyn7+3vQWPOVLdVgcLO8/aN70doZ0aTV4wFoJu5HM+fFs49La717Tdu3
8fBj/1BpzU37lrX/V7TW3ROPS2vfoX8wqfZLTXvVoJVX+nRPK3ooS7vmvyuSsT1YMlhPc6o3v3hU
Gq7RPBCRgBkPChqu0fxv0utpju3uebXzilbOhR6QFrE6TUYrWPBhQaM1GgYGM8BpecxjWKN9OzvK
KD/BJY03aJ/tR4M6Lc+im9WKhwUtQXUahzmcV7S8RotykkUiNmdpaTUFt7Rr/ucc/LScveMmLd1K
K1datDQ5iv6USXtObIDKVbMt7a2Ay/k8LQ7HlAcnLA1IlsiKRmo0VUaSBZzZA6sn1dk1O3tP/G+w
PDZfrWnrimeCOjQxHGIWlzTi0Bbi3YV/NS1oxKG9nmTZKDZfS1tlJFnCrKIlNRqBM/WbApPImha7
tMilBSFOY3tQdqDyqU5jUlJvWdISWaeRyX+I4ag8UtQoI8nVcmkP3J8gP3doM6B5RfPco34+ODSu
aCBjS/PApcUiUDRuaNXRQ0Mb4sux+lUFDTs0OTq9HN/w4iAWdmi3S4hoYDJe0UjjAHM59HNo9gg4
dmkqNSkslpZGXJp/qT8aWFpZRmxZU6lJ0lVJi7I6bbb8nxG5wAUtcmnJDlq0kWZShEcOLVQ0yIOh
pYFLC1QZXNnj/ScIHJpKzRGelDT3WKkdvv/39/8tP1vTTr5rabCJdmVoi2owXafZS1D88tf6xS5j
AmJlabikmQQm6Q2LFS01NNw8A1RFRUvRd817IvWivvzyFl5XtHfs1tCuNtLseQPqplpBM7s8oWVi
21RDNyyB84mlNRbC9jfQWBxVtNpMRNMCczwe1U462osbDO3npHEWeZauaTeWlmykGUkK7Qy9rtHE
UNGkbZc20PS5NZcGFY3r4/EOrfy1Mcn5D8zX/lk18Q5qtAtDO2vQ1tXApr4chf5C0o001UtQn9sm
cwNNBNtp6ntY8W028p9VtFvflBd/0qQ1Gg/IQh8EMftpVoOFEOR8TXsbrWmqXVMJjkWDVmVoKho0
u2dNi4tWuHkVgHnFoclQXwVjMq3ZeICiAbO0ZVUNDE31BkiebqaJYUzGwpzt2ETTJzI301Di0kSo
mpeC1mhygQvVztuxEHdraAQT1WIVNJWaFzWaHMV4tqapTP/Vo2FR1saKRsbZFprbvV8x1XMWo4Nm
RwVMqGFKQXObXDXy0FdKmX1SXDUehgZZrH6VpZ2AW0NnSzakBe3CmtL1VAC7fShax0BfaFWjXahO
PSuuDOfYpeUERbKgeQ2ajH3gdux7Erk0WLII7MVX/nmzGlSXPm2iUWdQlNa2Nbr3UB4hwuzPpdVg
pqCJ2ANW0IjbeKjBkvoOad/cormDK5eGt9NU/tVptU1UfWNBM7u8YifotBjVnnhuR6WGmCQvaVGL
RrbT0A4a3U4rB0V+a5cn1S+ytKGf+sLOGPyUtGjFQH4jzRbejTR/Ow3toNG8TqszUr99lnYHjWyn
OdOWBo3aYreJ5m+n6SsvN8bGAwv+Dlp9stegYb6VhnbQyPvQith4YKGYrtWvuyhpHjegjbRoO611
dem+NFSj1UM3E6+20+qTvQZt25O32rT11Ou9aaiav7nROPTXpDXmoV3S6vERjq9toYmuaLUCtJmG
Py5tR4beTZNd0e489EcOmJY9JE3fEJjHf/cHp5+jaXoH7c4MpQ9Ma0TjpOMNwBu5WkiG0Kc1WjHU
D05Z+vvm1ejxaU40abUwnaQZYDwUrX1e9UNo5shcscvrK3j7/R9P/wrVb796Lxor/niKnqZq8pD8
8EPOIkew8deuabT2Wwb4083XOTVpVeyoBrVLuVpfDxZ8f1oRD0RrVYM2LdW0ZpI+Lq3VeEQWnG4q
PuuP0T8BCq8fmUZ30NY19Li56aHK2l2p5pmjRG3aq+20+9TQD6VRS+PvmWoPRasVmtagOfoINL9x
IfH9Um0jzX9EWmNmvYtGLU08ZFnzGze8rGlkJ608Qlej4UelOXNEpxpspkl7rMBN1HqGUvfq/d00
5g4L1/vZRZO25Xdp5O3VA9Nip+Q4NO/eNH3AgUxZZmluRrxyaLy+6S4azn2E2Ce6IHxeo6lq4F4b
69Di9T3bJe3IY8830dYfe2+afnzqiocBCv2sSfP5p+qPwYm+AKBBc3ZkDtPYwzFps4zUaYF7Jfrd
tHNNG6IQ540M5VQG/hzFMeKUN2mgkvq5gvvoM9u9V7TYKQkujd2flsaRflQvPw5RSKBJSzJDk0hE
okHzYcrDEJ+nOIUmjWTP0O+lnw0U+kWTti4JT3fTcBoPcUE7ppO0TjtStNzQBBKJbNCwpU1SPIZ0
fTuIoUX68QIsDtXbZJOW8JdPFFwlSsTuoh1XtCFmdVrg8cw8wVTTspFDO1G0hfoQniDsF+c8ajQw
tBGK1VTGpU1BxIR4C66RO6fIioZshqqyNvSbNBkUtJUYOjRByLik4SV3aGl8SglaskGMVPo5tGNF
k5qmn8EB4q5U8201iGQYtmgnlib1vU9baZOxqNPULkekpI2GDi30Z0LGWG/1plLeRaMwhxVkUR5G
wOqNh8bYDAW+4k0a9iwtVf9t0gYVTdXkBo3HBW3GR/elgUszqaafZz0HCVLfAujSEFmYapCrhGvS
kM3Q0QbalK1p4R20QTQ/t7Sv2zT9cFVNy9o0Dxb6QxOYqL623ngoGrbVIBFtGkM2Q7k3Y7tpBOnG
YyxUQcNfR2+dxiNAFS1v03BJu5y0aPrlJcgki9U0sEGLbDWA8Qyyu2gjfI5WOm++Dt3uPfQ46IdG
z6UIVvBzl4aJTeoJXKtUa2RoScsVLXVpU/3YbqK2Lu5DIxOk2zX8daPxCHXjcWwaj2DFnT5UlUKy
0ElNJt41yblLGyQTYmjQonkVbaVpW2WKRlWG0kudakPytdPkYrWfhb4T29JU48Fc2lh9aIA1ze3e
za/Vt5CrgqYvuanTYn+WxZb2ZgY/3TUPTRN9/7KhqWpAiUMbKdrQ0OSw0eRqmqeSOjY0t/FQNGme
TWJoz5o0Hg8VfMm+nLHdfaihzU2TCyGFWjXAavImZWJokAt5Wqf5iuav9FICk7NrMm7Rit6ANGto
rG8Sju3zXu5oPHSq0YKWq5FHkwZgm9xMlOuSWBoBuHxtaXCNZw5NlZFRNDF9KI1j6tCkvknYtmtf
qib3rgzFqamhVB5jZ1BUp0mRtGhgqrVqPDA41UDtMgFDU9XgNKrT1HzwZpHZPvTNQmQ7aXH9sk9P
NmkJHOtBUcJFJBu0eUmb+FtpeaN71zSwNHi7AHl/GnLaNUUZBE90fRggfeNkvYbqhQ6yL3QNPc9V
zrMGLSpoWUyzbbQbBju79x20I02rb3JozimHU9SgEc/U0EgMiGjQViVtyRKxq13To7UPp7ntmltG
UofG0En8iRlKwhmj6R20XalWP1Hs9Ab3piHUpDnfv4v2cisNt1LNpfH6tj1p7QNlNVrzlXofyvbN
ULeMuDTnCuGdNXQXrRF12rOHydA9U23XimSt42t1WrOM1GiqUnRKa26r0Zqb9i5rB0s74FTrmpbB
zfJSfucvN9C6ztAdp+H0HZN6BR/xBthL8+Y1zb73XTK++md7L8jpo9PcaNJqkT847f1ugb0frfbq
O3b6hy/3pLWitkZE+yzye9NsrGnU/HuxVP/z3Gcvn7bL84fHZpp/P1o9Hp6WwHUE+Uaadw2NovC4
tM3h9vib4vxHb/U49xBpnUVP2yd62j7R0/aJnrZP9LR9oqftEwdMa188dTBxwLQDztCetk/0tH2i
p+0TPW2fOODe4IBpB5yhB0zrM3SfOGDaAWfoAdP6DN0nDjjVeto+0dP2iQOuoT1tn+hp+0RP2yd6
2j7R0/aJnrZP9LR9oqftEwdM2/Ww7z766KOPPn4p49EuDn3v8BpP/Dyg8A+X1liQ/ZACz7esPdp9
kPHh0sxidC/KNV0PKQIkaO7lKGEcoSPmSfrH9SUdyT90JkNHSES5Dwj04xWizM8jOAMfUsgIPPMy
ClO56Qm0jxKeomHF0Y9X0H/pBQLOgYFSEZxTmOUbll1+nKA8Csh8ttKPV4gU5RtFmwPXaxsQYlZm
6Kx9iXgYUH/JNC2kdDIW9Hw+4yJMqKKlksJk2RltEFDvin1haAFWNH8+5jwcEUKJok0m465oYhAE
3lWqlyQK6dAfiyNLiwkJMJMBnrQfnPo4gRWNgqKpzjTJQ73oJp6fLUyqBT6Lh35nqYZkSROWxj1V
9BXtlJDMZychItPOabKgEbjSqyrpBy4ZWlc1NEBxHNB38/TXIEMRlDR+ADSqaQHC6Zc8VNUg9DxO
l2bZBWJoItz4PPJHop2YxuONoo3I1/6U68bD0kJN87qiqZZL6CaX6weTxWSIp3zom3VB6ETXUBH6
s1VHtH99Iyg5X96sxEilGsVjPsR2yRLTrgn1r0U3NKyXbZ7AVC9WhBKgF2NOyRXc6OVxdG8gKD7v
qPHAsAQ10tA0vSYhAVUNVA0F0+SqPlTQi66aXA9moIZEaTI1TW5Juy5oSKja0FVvkOgHqGWqfVMd
lR5K2nbtkoWSEL1moBoYddWHIr0G3PpfAjGfff4JSp+/ePbUk+iVnz9F7CBo7ehwxoXvoHUYd6Va
h+H35wn66KOPPvroo49HCpJ+S5rjyi+f3Pnexww1L49ElHuQ6oWWj7zZf32epEl2jPRK1XoRbdTZ
pc0+8ETqA+BTs9yhfwUrYJAHSK9U7fGIoRMfSwKzxx9t+vZwslnMN8YEq6mxeiUfIrNSNU84ivEk
o5rGHpu2WoiRmqWfrWlvxbs8RGbNZa5mpjG56Ig2XfDhkEw8s8AqIXyImZBBQVtIqY+CDOn3uqCN
l/pZyhdoTdPL/Ra0JR+hkBwHgd81jRBa0IbIrFS91AcDg+5oTNF+omkJHEU8wCBFEEmzUvWSaZpe
3HzGOqaRiB9bWm5Wql5ChoKhlykadEMbVLQ3/JgYml2p2tBCS1s+Ng3XaKoNMTShaXr17zeaRkMv
17Sbx6ctuKJNimqg/henSNVQfK5Xql7CTxEJkabNH5+m+qOhXgLcNh4LTTvRNL228Jc6E4lEpoY+
Ok11leJU0c5sqmkak4amV6rWjQeRIe2ERr1Vlina1dLQpoq2eKPaNdU/LPkb3eRi0REt0o8VUCVL
H7q1NP1UpcAsFg16EW1FG0ZDfzbtgpbIEJ/BpaGNeejBrWk8FG2lj4n7guZ5FzVU0SI+wB4QvXi7
piGY88A8EgUWXNM4zbIu2jVFMws+PyfpQF9XwY5RdMaOqdQrVY95IhSNSE2bPTZt0Hws7/P6P1Ka
KprPTrzxrjXIP060aK3o7HLw+HBpd6da8fTzx4+XbuHaEJ3R7o79rh37P8q/52td80jgAAAAAElF
TkSuQmCC
</binary>
<binary id="img_2.png" content-type="image/png">
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
gg==
</binary>
<binary id="img_3.png" content-type="image/png">
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
</binary>
<binary id="img_4.png" content-type="image/png">
iVBORw0KGgoAAAANSUhEUgAABZYAAAEwAQMAAAAeqrakAAAABlBMVEX///8AAABVwtN+AAAO2UlE
QVR4nO3dTW/jSHoH8KrhbNcgaLgml0UfDNYAOeQ6c1oj4JCD5JBjvkLnCwQOkIMGK5h0HEC3FYJc
cti0PspS0IE5jb7AIi5FQLSHxbgUA9vVaDafPEW9kNSL2zIpiUT4H/SMm6bJ39DFqmKRLBHSpk2b
Nm3atGnTpk2bNm3a1C2UkK9yf5XkrUNenU2zlVfkbyBOAGAGiyS40JcWEB+KOYfOdn8VEvY73PsD
PBVcFSQ7hflr/HNFvo/h/knQM7Lb7HrljWJIviM/4sZEWeMOsx9aCbnsfjf947frHfIXU1lJ0M/+
PcD4H7yQvMWtXX6xvYfM1tmz/NDww5n/ej9RJK0Ivl0yntz9xfHNiQ9vHCHxW3+eX88Kdv749QZr
R45g/s+wUHXi73fnei832+uvVHHPn7Xty/buqjZn26vKvP2T9TdXcpyf3L2z/koWltegbAi5c4X9
5id/OU+msuPsx3v2cLX6gsrP7Pm5qcpM077QrmTm8DN7fm4ucG/FI3Sg+TX50uze+rTPvK6VKzUX
e1hPmX2zKg+99d6vsfp1U7Pa1+c5kjnIL7lMbXJjvYUZAPcrQtArQYcI0jW7Z6FYbKWwLfLFqviY
TRTNZepnmkI2zWpjvZVZEuKNTGmyVuYgNfOA4xe+YvoiYZT+RENtha6pTVRxE+u8/DijWbGV+ZvM
LPeYUeD1Pq7N11hS9MLMKAS+5rEANho9WBPNJISeysysaC5TP1NpzB1Lcpp8zqwZChwmbzIzG6rU
jEsAC41IBPRG0QObxGwC0tdZrVyp2RomFJgUd0Dvw9Tc3VpvYVbUmHngZWXDmsqlmRmzJ8RDNI3G
xjwFFXdz5uIWS5ojuIOeEj38j0zNWa9R3P7pm5zZ/KYdQcRXMZObZj6SWnf5oDecjvt8EvPZcK7c
rCWp1jxGbKREPxpheV2atTW6DbvEm8KHcG3WRHkSBuRCANdmwTWh70NjFoigUnW6bGDJxzFPzcEH
ZWdmHhQ6vSXL8xiiKFLewJp+0ksz1Ww6muBZ9Zg3d4jyFbxD4tpMsKZcmAVVqtNhAyof77mYJOIh
+FHaWbXEi41XqbpOinsrujNmOlVrMxbHaApD+Agf5NqsqYIYbGPuq8X/BPHJ0mwTNPvwjko/ufAW
Zkis6s1pmzKwRneR9t/RqezmzRFMtBY0M2O9rbuOMbNe+JSZexMQD3gdnxzhOKfm31ghQ/MAptJZ
1nXDmE/H/G4iOyJt8VbmiXIc15hHqQTLxg1ZnoM2mS/NXsw/TMBLzYT+bbDaWaFbUNKMzbEXxcYc
Fs30UV7lzQBDaRuzba3N/tLM3pG57Pjv7fQ4f5jMvjfm3+fr5QrNGlsDrxdDwZyI6X8J8hia6iBn
pirJm7FsAFm2g6m5Q9AMwOeT2Qc0mx89hllhScPj/Nv3g0+jgjlJzRfU9KEW5k+A/eRNc7Dsb7wj
jxLrund08v6eT9A8C35Udr5edvJ7LlfXYS0geTSYvcPGzVmdgyCmsb00q3VdJ27RbOfLBoVw2a/r
U2PmWGPOxn00z0397OZxV/k9lzWPsC0czLCuy5m9zKzXdR1fmPllZp6l7SCTHremYccVg95oFo2H
k+l8NtT6Zu+gTFmzNbubDSI8Qpl59v00MeavbTrNzGwoTdudM7NRarYg4dFsEnsgTAO1MD8qU71l
gzLX+T2XNNvG3I/6vVz9PPswBWN2bBp2MvNEvjb9jct1eV726/AKjcEYsCeKzU00eridPE6wj+TH
eweSyprZLIrQDKOsHUTzPcezqovm7tpsTZRt+nWZmd+m/Wfs7lvQx4ONfVHeM2YMSE/vHSwoWzZ+
N4No3OvBCDLzHHtnFFuJwnGmc+06PLzJyoYI4uX4Bk04NtXK7nLr9mcSTsIQAh7mzMXBgtL1xhR6
vd4ddu9W/To6nWMv+G7+Qdt0kpVnquPYYRCjWS3M3uJ6cMEwAwZZUcBrcpM9gwUlzRwmYPUsElvL
/jO20al5JgF7k7l6g8QQOxZorOuW/ToXG++1mbgF8yJ7LrxLmpmWb9K/WeNgaZ7jKTSawcKsMnPX
1w4BmZlvFmMFS4b9lLl44V3VeJ3Z6tI8QfMU20d5QUzHbGG+Jl1POcQLL63bpdkhv1xv5GKnec+F
d/VmOQmHwQRwy6937G/BJatjuNrIK7PyllmuvihexFZtXuVN9p3C/tYpmtNsmvddEB7LnPtOsHPx
DvNmmmjOzrzialWN5Z7SfLZx/meYszOvWNLPNmZ+gPnLupifcd+Kpz8p8WrH+wPAJ3gP4Kd3Tw9h
FlLFcabi318rRHzcfa+ZYJ/Ex2vOwNtcfmQzC9++wn//R3T4/fHKzRtnL7OU5x/OeipJalYUsPu4
yP/MR7/9678qMWZeLTCfwU/ijaabNwwqSCnz4NG6Juor84jUKfM888ff/9t35I26DHZsQZ4WTLLn
ZD7hH7+nSXjoBvbeM65v6Nadl/pHFG/UNSEWNM6M5OJN3AbEnMHnNhwa3kAzg72PbdQ2FDYe/mhC
mmj2QJ+bcHBEA8186+GU+oc3srshz004OF7juhvLB8GaFaob10UiTDauu0FE6J+bcHC84ObchIOT
kMaZaVx8vKEJsXTzzDwsPpLRhHhB88yNu65KT8HGhTWv70y4PLfg8OAp2Lg0r6/RyFPQat4ld3sK
nijNu0Rp5CnIGngKNvAmWyNPwQZ2RBvZojSwI9rE4tzAFsVqW5STpIktih+cW3Bw2g7SadIW55OE
NrCDxBp4CooGdpDa4nySNLF29uS5BQeHNu8mN2ENLM68gcXZl+cWHJxGFue24T5JmvcIErEaOLIh
GlicT9hwL17H/UXp7VjV1nQ8JMN/+adf/POf/MHeN3pk6Z1UWTTEs95CKl1P+VU23KcxV9sIPmWO
NP/fxdyDZc280j6d+OnXY02Dz6xV1uxX2ghuzROwMyV3aVXbCJ7EXG3ROI252qJxEnPFDcpJzKLi
K8ETmCt/KfMEZlH1VdUJzJW/9LM2W8ET7y+XMbPKr1Ayc7gxO3Q+Zcxe5WMxmVlacu9aJcy0+vfB
jm6u/AzMzUGRm2h4OyXMR3jt7tjmIxzm3Fwfmv2GkS5JiGv98Qc/8OPcNCAvNm9Oe15Jsvq5wzm/
A4r/9GAOISS5qvvFZn6MoZgM1uVc9M30yBDBI0hI3Gytl5qPcphzZsfmgqO3BzN4/Kg+JbmXSF5q
Ps7LjZnZtbkjelE0HkmXhUrb5c37PzKgVDKz7aDZinrjWzQHHVWB2TvOO6SZmTu8c2GmGqZLc648
A7x/wbb5kaYlyJm7XBMWeoElbQZa2V7WGqRDB8Gh2z7WK6SZmWmuKIR+APJyYc4Ko78c8QgO2bR/
rDf8i2YO0u+BuuSpOfvN/no1THPAOcWONjFIZrYU12j2otSsjHm9T0W+WA04Pfd0pBWM8u1Jzix5
LKYhH8+lwwKC9UY2h1y6d+EfovaON8dG1qlAcxfrOgvrOtReb5sXE8E8U+0fccqYgtl1rfHdPUVz
qAtmufriL55ZrNnxSkahbIRcuCyK7i00Tz5g/byY984kt39+/4wq5Lgz8+TqDTQLPoNxTzm4z41z
cAP09LE252tYOXWdzOwHnA+wfzQG5VB4X6jrNn7Py5Mxkbu36R2XnDMDMeYp9NFMIJL5NmWrbP7l
Uv3N9hbTX4M8hnWVvPmV1YdJwrB+Jt5deCmytnvH+bRqZb4tLra8Ku5lPJ1c/9nsMfyBUPnNm821
dhpWDXquHNDFgreVMwspNfblrW+9JPn7MLI63s6UHK/zYCv/XZltX8qOMb7+VVF8ioeOKhkXXYFn
FYCekZPcT6k4TTTvnjN2M/UyN/E4G/MP5oPo/uzvv3zl/wz9ZLPuOn67dmjELmJrrjzPMQ8e5bmZ
haxm68VirBNy3dnxEc9t2rRp06ZNmzZt2rRp06ZNmzZt2uRT/IBQM1xa83TSj6fOws7xEVSHhUFQ
fEyS122gfDsCZPEJ2gaYPVCi8DAcr3/Z8HraKyx4+Wdtniw+1cV50UVwHsgB6ZLY+YFLQsmF5QGx
tDdz3vZD6zE4t2x/NOl6iZCEqwvzlCePvVkyB3WUR66riiaOB2gWWgg0i8SLYG7Oy/Dcsr2hirg+
DCTxYm7M6Gd4nHXtzf2hJDcudy1JXMHZH+aq49b4VESzfcPQrAV3KJo5Z+FcdZ3sEbvaZWlWRHPe
QbNHOAvm0ukWP/C7VjHmrjUEqTjXaBZyYe40wKwAuDLmuBFmt2tNQC/NHJpiHkIMIEzZsIDzYB42
way68LOIzbuPfr/2ZtMOOtZQOoqILpqveJ8Zc53rDRKTrov1s6O5cNHsLM1ujetn7Ndpl2PZwO6G
SNuUfpSaa9wOkhsL+xa3EHe91OwBnxnzMd5YrCzptRWF2E8Ev8X6OTXLepvxGpajWXuJnZoT9mDM
vM5m7NyzhOLFd3zB0MxiGpFJeEWi4Nyy/TFTW70l5iPrCouv6lzXkW93Lq1zX3RfvAbO8+41cTa0
Bs5V2cQZ3Ky6jzO2adOm2dn7Cdc1nqya75uGRdS3NeX73pv369sL5/teEIP6ThjPA3f3N2IenhRy
QDgx5u7WcqqYPLXlueHmpSK84AF9fWFpK2bkrZlhInhytqgzB81CcTPi0BEs5sACpVxuzDQ8t21f
0OxpDkM0ezwRqflGoDk8t2x/sDz7ieilZoHmHkGz16+5Oem6woxUdnwhXGbMicvqbYZY2xcLs23M
WDYShwVPzCN29qBZcWJGVztAxKUxz81xrrdZa58Cmv8RSEI8NE8Tr+7mEIDDRDo/Ak2YOQcf8VSs
tzkgS7M/pqbeCBS8q31dh2YxHRozAQ8iYzbt4FOzU5456SNMaV3n94nn9YzZTtvueptfuamZX3KX
RUQlNo9q3t8gmqd3knzucofhcU5s0Qvq3a8jsRB8Kp0bLoTDo1sV24IFVNe5/0xusE/0kJrB8yPQ
sY1tCjnmvEMlg2bsIxlz15gBzTo1+zW+hiXm0SB4CK+63PRFI9P/MHdyazy6imahrfDvgqvrV0zz
OZrl66+tWo9vbMf5/Cq1S2s+TZpo3prPqk2bNm3atGnz/y//B6Mua5QKffvBAAAAAElFTkSuQmCC
</binary>
<binary id="img_5.png" content-type="image/png">
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
oxmLfqFZ/z99TDUaci7XVAAAAABJRU5ErkJggg==
</binary>
<binary id="img_6.png" content-type="image/png">
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
N7nJSfkfyLcVFTefPt8AAAAASUVORK5CYII=
</binary>
<binary id="img_7.png" content-type="image/png">
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
9xffMcb4iuJfFCuKpZHj3MMAAAAASUVORK5CYII=
</binary>
<binary id="img_8.png" content-type="image/png">
iVBORw0KGgoAAAANSUhEUgAAAUIAAABIAQMAAAC5j24NAAAABlBMVEX///8AAABVwtN+AAACK0lE
QVR4nO3VTW7UMBQHcLtGWEgoZolEFXMElrOwkrNwA3ZEYiCp2JcLIDhKzQU4QmVOgKUuiEQm5r3n
xPmYCgYpSJWoF5729Vd7/M9Lwtj9uB8bDSH1qxAOz+q9OTPi9aOOPZ/+ePbiAcyP2Tn+IsMpo/9X
kr4Qd/RhaFbzUoXTkzxJFstPaR7+2dLHjubsbsndHZBqizXPSQYqlxZKpZUtyoxKwS+lCA2VHZRq
r3qUGiWfSYNSBgt5Ylmy0OqAMl/LDqX6QNKD5L4tNMqCZJckj1I4kMK1IN3eqAuQNUmXTiRIaqjs
mLAVU9wZI1H2KylJ5pMUNq/ElyTtJD+izEhi6ybJSYpJPswpz0H6SYprkhDfePYoBUk5l19xCwER
T8njt4ZdQCqHJyqMBCkvUcpZnnFNGSWsyf27QoFUGclv7UoqupqqBdn15WeQWtHudi11bByQLPQ6
gMyzeBdXK1lEuQdZ9wploUepFl3XRmmgXH6SNyDrYMeI55LPJFTEBcrxYix2F55ibkgaJgcJZ2/m
EvKEkOjS7acOKWnNI6midCSHrqM45CKlmcQ8fVUobCEVk/dLqWkrHlCGtgxJinAk/XDTQPJej3dc
BaVVh0TJaofJO9UlySDVhVQ/KbyyAakb4YdWB1mylHx6VN/ytKlSaWuZnSDXz7rb3jK/laevuYH8
y7MvX6Vv+usQrr4fupc3Pxpx6Lq37y+hL/Jjuclbm8efrv4s/+PxCwWnF1+Eih4qAAAAAElFTkSu
QmCC
</binary>
<binary id="img_9.png" content-type="image/png">
iVBORw0KGgoAAAANSUhEUgAAATcAAADYAQMAAAC0iTaSAAAABlBMVEX///8AAABVwtN+AAACm0lE
QVR4nO3ZMW7bMBQGYAoMws1cOhZSr9BNg2BdpUfo6EGo2BN4LlAgR6k69Rhlp6wKMlRABbPko2NZ
Mu38RggIQUQghh1/IcXHp0cRYWxpS1vazO1RIypjD5hLHlrM/cEc15Bbcd0BjDPe9AAzd6Kpojqp
UPcFcZlUa8C1Mzn9Bh2ybjoTUB5YB+UV6GwTDXJ/uC7jOrAe4A6rV5I9NpBT0KiLW9ziFje3+yDV
J8QV8qtGXCm3kMuMaSI7hTgZ2QljEAY7bnZRXWKAp9OrHLZLM9hhuzSrI7tSR3YN5rIXOptI51rt
XvTzjlp7rVN37beG97Xe7syPC45eE3/1OX2W9J77X30eOXZwt6BjcZyeqb9Z3HtaD0PO5unBCVvj
Jk7byqKcs4kkqGfr5MQVLi7CFubc1oOW+qtCrndObsm13m3I3Y9c4h3XbjR7CnCuIzeeB+/dPLKE
XLNxX9s/PXWC+kuPHaf+VhP33fW3Iueu7ODSsbtNKX57R/MQ5LJ+HGfvaB5MkJM038yE1oO7uDCp
yVGcy0n8Uso/4R31l3onm8C4ktZNUvwKcqkIucznC7mqoHzhIbf2rnJuk4ecn0fnXeFcV511yZFL
7G4YHNfmM28p69TeKWuK0+vTLiy0vtVFZ5PDO03z8G4disuT64Z8LuV0PVz+GU15713h1zfo2qP7
o/D3RyAPvGO1HsYVu4CT/yivSjU4F9JxPRjqLvP1IA/XjRN3pr68OafPuQv75W/7c//rr9m9u2Ef
r95XQcf9x/o5N27q6c1N8OtX1xIV1+0XN5oTOq6T4NNsbJeBT++xXYmdQmBXY6ck2IGnONQluFMx
HQdP1agTw7kjipO4w/7pI35CCZjyJkdc9LqxuMW9Biciu9G4/wFetoVkwSORMAAAAABJRU5ErkJg
gg==
</binary>
<binary id="img_10.png" content-type="image/png">
iVBORw0KGgoAAAANSUhEUgAAAk8AAAC9AQMAAACQzAsWAAAABlBMVEX///8AAABVwtN+AAAGTElE
QVR4nO3aPY/bNhgAYDIMwgzBsZ1riPczNChWtvZn5Ad0MNBFBYSTjAB1hwLJ2Kn9G91C44Yb7w8U
CA83eFWbRQFUse9LytaH5drSOcgV0Av4i0c9+uDLj7NMyBRTTDHFFFNMMcV/x5OzSZrPqncX7uXZ
WIlqsaU89yLgMYtGUZ7XpiQ8An8cJaq3F/VRBcEoKuhQ+OLPDtXvjyeOinnaojAir6f+oYNRMyL1
1zl5qaMG9bT682IIpeckzMKSGKAUFMyoElckVExdPC9IJo4BbSrJw5IanTGkPKaFSRMtSyEN0YOo
gBRxaFiDkiY1WWiAonp70qdQWUyz+Vyw6x2l5Gqd55ZaDqEIUt5crJZaUw2fJVeS3ev4ai4kX6pB
VATJGfDV8s5RAih6r4PIA+p6EBXl1JQBf0t3VOrRexXknvAYUGoAlbUp/hYpU2SOYgOoH/coUlFy
KPUSqD/9BvX7aCpEoUGxD5b6A6jZcCrzmlSJlCKZ5w2lpIIU9fmqQVXJMIJK8yvf5pXdjBZEQurH
0VwgNSSv5FqbAqjru6yiBHScrLi6kkMpsdRJEfA32+6MlFkaGCyAWg+ksjBvUCSXODIg5bH1oO7M
YdZSPsfxym2WS1GSJBOlsNTpEsQr+5xsB+TFBbkkMiXzZzOWDhn6GhG1N7Oj+pABuRFBazN/bqmL
/rrHKNn8FBb4PGTyaoQ376HGzM4w/bU2kzk+j5roYRHUmtRFZqkxK5lO+CzFl9li6Ib7C77tYuhy
KLWf02PWVRM1URM1URM1URP1v6BY6rNx0iXvLDOY9mV/1WPhc6JbBbSMzDgq5KxDGVOOo6QxbYo8
BkoYo9oliSkeA5W2S0KTj6P4WalOyWiK7VHSZGOpbtOfkRLdRDs16F7Tn5Hi3UQ7Oc5JdZuedRPt
5EjOSHWbno4c+c5KhbpLjRyueigympJnpFS3JB5LiTNSabdk9PcL+9T4NUOben35In6nCHn+6sGU
eVjoWqIPpPLPQ7kjdN832aXXJdXfQzkUvaaBFD+9vzcv1x+N+XBr/oE1gROSw5RfU4Ts6T07bG84
nPIPFR2herYLDtUaflSPn/pCl/3zUsdPkNsSGKjhDYf01awgSRrYKZNDOSugloSeG9j7t8cpmNTg
jSzwrrShBimDVJhzQ3ZUYneI38niDk2XEpay25EQ5gwtzBujtlRSCJPuKFPvUBTUqA4VIkX/stTc
g0pSvNlo2K6MoCiPpVg6Cmo5yu5QlszoDpVYSsdYKYaLoT1+s86AyvDks7nH13gZ8KhoZncYz5EK
+XWXMkgxOAzubg9oj92kNeXhfQt4p5DSka2FO8R7llmboo5SNRXQTboIbFbgnZGA3mk7kNeUrcVX
aZfa2BN0FKkosghc6+4o0aDwhosO2FsSdahVapPPUV5FRbAdLAdrSpIWRbW/T7lDx0kaqbJJZQ3K
I/Vlr6jbfcolrd0fLexl36RAMZtXmfQYUnNS55U9wV4qtZTdH7UZwzdpDlRWYF6FkiMVOEq7y26v
1U2a91LU7g+bU8vbzToPqlQrE3mLVNw4qlgiBdnXotSWYnZ/eHWh49ybfNsSiREGqcheK9e9kjDF
vLr52E9BCtpkSLE7A+VXVGi4pXKXyIXtOLCo1tJcux02KTfIuOEDLyj7pGtK3rJPSGX4kSnbztJR
a7N3VJYKawpKZ4stRS4IXdeULRIbe+yqjHpP8MpSC0sxRbJdX/II9kFolbp7wQPa6z4li94WzMmu
maE711TVnbFiTQn8DNtlfRR1VGwpfoMdldlkqAaZFhUh5e935zYlbYquljASMdtL8kDyZU3Fu0EG
f83QSzFTNTNSZrXRfpVEpsBfYFTUduiznV7A0fZTZWNsZ3bUtqmd5AKnCeaoKkWxVhjy9oC8TYaK
EnbGKWHG8avpJczg45aqOg7em7Rju+qjqLFXgeO0BNettFmbQpFQNMdRTbkpzvYJmeOvpQq3wyZF
Tl1f7foE13jsGeTiYOpY0Zehji0zH0b1LD9Ooo7pZ6dO2u60yz78EA5SH83fcezFUFvBuK7xfxxI
+K92q8XU1nLfTbgicegEz/if16Ok+uMFPiny7c0v5En489PXhP9G/IR8t1Kb9++S2x/uvglCUf6a
mL0veKaYYoopphge/wJnX1WInZ/5hwAAAABJRU5ErkJggg==
</binary>
<binary id="img_11.png" content-type="image/png">
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
H58l/gfoAetFVvxDeAAAAABJRU5ErkJggg==
</binary>
<binary id="img_12.png" content-type="image/png">
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
UDfOOgAAAABJRU5ErkJggg==
</binary>
<binary id="img_13.png" content-type="image/png">
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
AABJRU5ErkJggg==
</binary>
<binary id="img_14.png" content-type="image/png">
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
</binary>
<binary id="img_15.png" content-type="image/png">
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
QmCC
</binary>
<binary id="img_16.png" content-type="image/png">
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
</binary>
</FictionBook>
