Эффективное использование STL — страница 58 из 63

,class std::allocator> на NicknameMap.

Далее приведены некоторые рекомендации, которые помогут вам разобраться в сообщениях компилятора, относящихся к STL.

•Для контейнеров vector и string итераторы обычно представляют собой указатели, поэтому в случае ошибки с итератором в диагностике компилятора обычно указываются типы указателей. Например, если в исходном коде имеется ссылка на vector:: iterator, в сообщении почти наверняка будет упоминаться указатель double*. Исключение составляет реализация STLport в отладочном режиме; в этом случае итераторы vector и string не являются указателями. За информацией о STLport и отладочном режиме обращайтесь к совету 50.

•Сообщения, в которых упоминаются 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). Если эти функции не вызывались в программе, значит, они были вызваны из других функций (косвенно или явно).

•Сообщения с упоминаниями binder1st и binder2nd обычно свидетельствуют об ошибке при использовании bind1st и bind2nd (bind1st возвращает объект типа binder1st, a bind2nd возвращает объект типа binder2nd).

•Итераторы вывода (например, ostream_iterator и ostream_buf_iterator — см. совет 29, а также итераторы, возвращаемые back_inserter, front_inserter и inserter) выполняют свои операции вывода или вставки внутри операторов присваивания, поэтому ошибки, относящиеся к этим типам итераторов, обычно приводят к появлению сообщений об ошибке внутри операторов присваивания, о которых вы и понятия не имеете. Чтобы понять, о чем идет речь, попробуйте откомпилировать следующий фрагмент:

vector v;// Попытка вывода содержимого

copy(v.begin(),v.end(),// контейнера указателей string*

ostream_iterator(cout."\n")); // как объектов string

•Если полученное сообщение об ошибке исходит из реализации алгоритма STL (то есть если код, в котором произошла ошибка, находится в ), вероятно, проблема связана с типами, которые вы пытаетесь передать этому алгоритму. Пример — передача итераторов неправильной категории. Попробуйте откомпилировать следующий фрагмент:

list::iterator 11,12; // Передача двусторонних итераторов

sort(11.2);// алгоритму, которому необходимы итераторы

// произвольного доступа

•Если вы используете стандартный компонент STL (например, контейнер vector или string, алгоритм for_each), а компилятор утверждает, что он понятия не имеет, что имеется в виду, скорее всего, вы забыли включить необходимый заголовочный файл директивой #include. Как объясняется в совете 48, эта проблема может нарушить работоспособность кода, успешно компилировавшегося в течение некоторого времени, при переносе его на другую платформу.

Совет 50. Помните о web-сайтах, посвященных STL

Интернет богат информацией об STL. Если ввести в любой поисковой системе запрос «STL», вы получите сотни ссылок, часть из которых даже будет содержать полезную информацию. Впрочем, большинство программистов STL в поисках не нуждается и хорошо знает следующие сайты:

•сайт SGI STL, http://www.sgi.com/tech/stl;

•сайт STLport, http://stlport.org;

•сайт Boost, http://www.boost.org.

Ниже я постараюсь объяснить, почему эти сайты заслуживают вашего внимания.

Сайт SGI STL

Web-сайт SGI STL не случайно находится в начале списка. На нем имеется подробная документация по всем компонентам STL. Многие программисты рассматривают его как основной источник электронной документации незав4исимо от используемой платформы STL. Документацию собрал Мэтт Остерн (Matt Austern), который позднее дополнил ее и представил в книге «Generic Programming and the STL» [4]. Материал не сводится к простому описанию компонентов STL. Например, описание потоковой безопасности контейнеров STL (см. совет 12) основано на материалах сайта SGI STL.

На сайте SGI программисту предлагается свободно распространяемая реализация STL. Она была адаптирована лишь для ограниченного круга компиляторов, но поставка STL легла в основу распространенной поставки STLport, описанной ниже. Более того, в реализацию STL от SGI входят некоторые нестандартные компоненты, делающие программирование в STL не только более мощным и гибким, но и более интересным. Некоторые из них стоит выделить.

Хэшированные ассоциативные контейнеры hash_set, hash_multiset, hash_map и hash_multimap. За дополнительной информацией об этих контейнерах обращайтесь к совету 25.

Односвязный список slist. Контейнер slist реализован наиболее стандартным образом, а итераторы указывают на те узлы списка, на которые они и должны указывать. К сожалению, этот факт оборачивается дополнительными затратами при реализации функций insert и erase, поскольку обе функции должны модифицировать указатель на следующий узел списка в узле, предшествующем тому, на который указывает итератор. В двусвязном списке (например, в стандартном контейнере list) это не вызывает проблем, но в односвязном списке возврат к предыдущему узлу является операцией с линейной сложностью. В контейнере slist из реализации SGI функции insert и erase выполняются с линейной сложностью вместо постоянной, что является существенным недостатком. В реализации SGI эта проблема решается при помощи нестандартных (но зато работающих с постоянной сложностью) функций insert_after и erase_after. В сопроводительной документации говорится:

«...Если окажется, что функции insert_after и erase_after плохо подходят для ваших целей, и вам часто приходится вызывать функции insert и erase в середине списка, вероятно, вместо slist лучше воспользоваться контейнером list».

В реализацию Dinkumware также входит односвязный список slist, но в нем используется другая архитектура итераторов, сохраняющая постоянную сложность при вызовах insert и erase. За дополнительной информацией о Dimkumware обращайтесь к приложению Б.

Контейнер rope, аналог string для очень больших строк. В документации SGI контейнер rope описывается так:

«Контейнер rope представляет собой масштабированную разновидность string: он предназначен для эффективного выполнения операций со строками в целом. Затраты времени на такие операции, как присваивание, конкатенация и выделение подстроки, практически не зависят от длины строки. В отличие от строк С, контейнер rope обеспечивает разумное представление для очень длинных строк (например, содержимого буфера текстового редактора или сообщений электронной почты)».

Во внутреннем представлении контейнер rope реализуется в виде дерева подстрок с подсчетом ссылок, при этом каждая строка хранится в виде массива char. Одна из интересных особенностей интерфейса rope заключается в том, что функции begin и end всегда возвращают тип const_iterator. Это сделано для предотвращения операций, изменяющих отдельные символы. Такие операции обходятся слишком дорого. Контейнер rope оптимизирован для операций с текстом в целом или большими фрагментами (присваивание, конкатенация и выделение подстроки); операции с отдельными символами выполняются неэффективно.

Различные нестандартные объекты функций и адаптеры. Некоторые классы функторов из исходной реализации HP STL не вошли в Стандарт С++. Опытным мастерам старой школы больше всего не хватает функторов select1st и select2nd, чрезвычайно удобных при работе с контейнерами map и multimap. Функтор select1st возвращает первый компонент объекта pair, а функтор select2nd возвращает второй компонент объекта pair. Пример использования этих нестандартных шаблонов классов функторов:

map m;

// Вывод всех ключей map в cout

transform(m.begin(),m.end(),

ostream_iterator(cout,"\n"),

select1st::value_type>());

// Создать вектор и скопировать в него

// все ассоциированные значения из map

vector v:

transforms.begin(),m.end() ,back_inserter(v),

select2nd::value_type>());

Как видите, функторы select1st и select2nd упрощают использование алгоритмов в ситуациях, где обычно приходится писать собственные циклы (см. совет 43). С другой стороны, вследствие нестандартности функторов вас могут обвинить в написании непереносимого и вдобавок плохо сопровождаемого кода (см. совет 47).

Настоящих фанатов STL это нисколько не волнует. Они считают, что отсутствие select1st и select2nd в Стандарте само по себе является вопиющей несправедливостью.

К числу нестандартных объектов функций, входящих в реализацию STL, также принадлежат объекты identity, project1st, project2nd, compose1 и compose2. Информацию о них можно найти на сайте, хотя пример использования compose2 приводился на с. 172 настоящей книги. Надеюсь, я убедил вас в том, что посещение web-сайта SGI принесет несомненную пользу.

Реализация библиотеки от SGI выходит за рамки STL. Первоначально ставилась цель разработки полной реализации стандартной библиотеки С++ за исключением компонентов, унаследованных из С (предполагается, что у вас в распоряжении уже имеется стандартная библиотека С). По этой причине с сайта SGI также стоит получить реализацию библиотеки потоков ввода-вывода С++. Как следует ожидать, эта реализация хорошо интегрируется с реализацией STL от SGI, но при этом по быстродействию она превосходит многие аналогичные реализации, поставляемые с компиляторами С++.

Сайт STLport

Главная отличительная особенность STLport заключается в том, что эта модифицированная версия реализации STL от SGI (включая потоки ввода-вывода и т. д.) была перенесена более чем на 20 компиляторов. STLport, как и библиотека SGI, распространяется бесплатно. Если ваш код должен работать сразу на нескольких платформах, вы избавите себя от множества хлопот, если возьмете за основу унифицированную реализацию STLport и будете использовать ее со всеми компиляторами.

Большинство изменений кода SGI в реализации STLport связано с улучшением переносимости, однако STLport является единственной известной мне реализацией, в которой предусмотрен «отладочный режим» для диагностики случаев неправильного использования STL — компилируемых, но приводящих к непредсказуемым последствиям во время работы программы. Например, в совете 30 распространенная ошибка записи за концом контейнера поясняется следующим примером:

int transmogrify(int х); // Функция вычисляет некое новое значение

// по переданному параметру х

vector values;

// Заполнение вектора values данными

vector results;

transform(values.begin(), // Попытка записи за концом results! 	values.end(),

	 results.end,

	transmogrify);

Этот фрагмент компилируется, но во время выполнения работает непредсказуемо. Если вам повезет, проблемы возникнут при вызове transform, и отладка будет относительно элементарной. Гораздо хуже, если вызов transform испортит данные где-то в другом месте адресного пространства, но это обнаружится лишь позднее. В этом случае определение причины порчи данных становится задачей — как бы выразиться? — весьма нетривиальной.

Отладочный режим STLport значительно упрощает эту задачу. При выполнении приведенного выше вызова transform выдается следующее сообщение (предполагается, что реализация STLport установлена в каталоге C:\STLport):

C:\STLport\stlport\stl\debug\_iterator.h:265 STL assertion failure: _Dereferenceable(*this)

На этом программа прекращает работу, поскольку в случае ошибки отладочный режим STLport вызывает abort. Если вы предпочитаете, чтобы вместо этого инициировалось исключение, STLport можно настроить и на этот режим.

Честно говоря, приведенное сообщение об ошибке менее понятно, чем хотелось бы, а имя файла и номер строки относятся к внутренней проверке условия STL, а не к строке с вызовом transform, но это все же значительно лучше пропущенного вызова transform и последующих попыток разобраться в причинах разрушения структур данных. В отладочном режиме STLport остается лишь запустить программу-отладчик, вернуться по содержимому стека к написанному вами коду и определить, что же произошло. Строка, содержащая ошибку, обычно находится достаточно легко.

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

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

Сайт Boost

В 1997 году завершился процесс, приведший к появлению Международного стандарта С++. Многие участники были разочарованы тем, что возможности, за которые они выступали, не прошли окончательный отбор. Некоторые из этих участников были членами самого Комитета, поэтому они решили разработать основу для дополнения стандартной библиотеки во время второго круга стандартизации. Результатом их усилий стал сайт Boost, который был призван «предоставить бесплатные библиотеки С++. Основное внимание уделяется переносимым библиотекам, соответствующим Стандарту С++». За этой целью кроется конкретный мотив:

«По мере того как библиотека входит в "повседневную практику", возрастает вероятность того, что кто-нибудь предложит ее для будущей стандартизации. Предоставление библиотеки на сайт Boost.org является одним из способов создания "повседневной практики"...».

Иначе говоря, Boost предлагается в качестве механизма, помогающего отделить плевелы от зерен в области потенциальных дополнений стандартной библиотеки С++. Вполне достойная миссия, заслуживающая нашей благодарности.

Также стоит обратить внимание на подборку библиотек, находящихся на сайте Boost. Я не стану описывать ее здесь хотя бы потому, что к моменту выхода книги на сайте наверняка появятся новые библиотеки. Для пользователей STL особый интерес представляют две библиотеки. Первая содержит шаблон shared_ptr, умный указатель с подсчетом ссылок, который в отличие от указателя auto_ptr из стандартной библиотеки может храниться в контейнерах STL (см. совет 8). Библиотека умных указателей также содержит шаблон shared_array, умный указатель с подсчетом ссылок для работы динамическими массивами, но в совете 13 вместо динамических массивов рекомендуется использовать контейнеры vector и string; надеюсь, приведенные аргументы покажутся вам убедительными.

Поклонники STL также оценят богатый ассортимент библиотек, содержащих объекты функций и другие вспомогательные средства. В этих библиотеках заново спроектированы и реализованы некоторые концепции, заложенные в основу объектов функций и адаптеров STL, в результате чего были сняты некоторые искусственные ограничения, снижающие практическую полезность стандартных функторов. В частности, при попытках использовать bind2nd с функциями mem_fun и mem_fun_ref (см. совет 41) для привязки объекта к параметрам функции класса выясняется, что при передаче параметра по ссылке код, скорее всего, компилироваться не будет. Аналогичный результат достигается использованием not1 и not2 с ptr_fun и функцией, получающей параметр по ссылке. Причина в обоих случаях заключается в том, что в процессе специализации шаблона многие платформы STL генерируют «ссылку на ссылку», но в С++ такая конструкция запрещена (в настоящее время Комитет по стандартизации рассматривает возможность внесения изменений в Стандарт для решения этой проблемы). Пример проблемы «ссылки на ссылку»:

class Widget {

public:

int readStream(istream& stream);// Функции readStream

		// параметр передается

};	//по ссылке

vector vw;

for_each(	//Большинство платформ STL

	vw.begin(),vw.end(),	//при этом вызове

	bind2nd(mem_fun(&Widget::readStream),cin) //пытается сгенерировать

);//ссылку на ссылку.

//Фрагмент не компилируется!

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

Если вы интересуетесь потенциальными возможностями объектов функций STL и хотите познакомиться с ними поближе, поскорее посетите сайт Boost. Если объекты функций вас пугают и вы считаете, что они существуют только для умиротворения малочисленных апологетов Lisp, вынужденных программировать на С++, все равно посетите сайт Boost. Библиотеки объектов функций Boost важны, но они составляют лишь малую часть полезной информации, находящейся на сайте.

Литература