Эффективное использование STL

Мейерс Скотт

Замечания по поводу платформ STL от Microsoft

 

 

В начале книги я ввел термин «платформа STL», означающий комбинацию конкретного компилятора и конкретной реализации STL. Различие между компилятором и библиотекой особенно важно при использовании компилятора Microsoft Visual С++ версий 6 и ниже (то есть компилятора, входившего в комплект поставки Microsoft Visual Studio версий 6 и ниже), поскольку компилятор иногда способен на большее, чем прилагаемая реализация STL. В настоящем приложении описаны важные недостатки старых платформ STL от Microsoft и предложены обходные решения, делающие работу на этих платформах значительно более удобной.

Дальнейший материал предназначен для разработчиков, использующих Microsoft Visual С++ (MSVC) версий 4-6. В Visual С++ .NET перечисленные проблемы отсутствуют.

 

Шаблоны функций классов в STL

Допустим, у вас есть два вектора объектов Widget, требуется скопировать объекты Widget из одного вектора в конец другого. Задача решается легко — достаточно воспользоваться интервальной функцией insert контейнера vector:

vector vw1,vw2;

vwl.insert(vw1.end(),vw2.begin().vw2.end()); // Присоединить к vw1 копию

// объектов Widget из vw2

Аналогичную операцию можно выполнить с контейнерами vector и deque:

vector vw;

deque dw:

vw.insert(vw.end(),dw.begin(),dw.end()); // Присоединить к vw копию

// объектов Widget из dw

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

vector vw;

list lw;

vw.insert(vw.begin().lw.begin().ww.end()); // Присоединить к vw копию

// объектов Widget из lw

set sw;

vw.insert(vw.begin(),sw.begin(),sw.end()); // Присоединить к vw копию

// объектов Widget из sw

template

typename Allocator - allocator > // Шаблон нестандартного

class SpecialContainer {...}:// STL-совместимого контейнера

SpecialContainer sew;

vw.insert(vw.begin().scw.begin().scw.end()); // Присоединить к vw копию

// объектов Widget из scw

Подобная универсальность объясняется тем, что интервальная функция insert контейнера range вообще не является функцией в общепринятом смысле. Это шаблон функции контейнера, специализация которого с произвольным типом итератора порождает конкретную интервальную функцию insert. Для контейнера vector шаблон insert объявлен в Стандарте следующим образом:

template >

class vector {

public:

template

void insert(iterator position, InputIterator first. InputIterator last);

};

Каждый стандартный контейнер должен поддерживать шаблонную версию интервальной функции insert. Аналогичные шаблоны также обязательны для интервальных конструкторов и для интервальной формы assign (см. совет 5).

 

MSVC версий 4-6

К сожалению, в реализации STL, входящей в комплект поставки версий 4-6, шаблоны функций не объявляются. Библиотека изначально разрабатывалась для MSVC версии 4, а этот компилятор, как и большинство компиляторов того времени, не обладал поддержкой шаблонов функций классов. При переходе от MSCV4 к MSVC6 поддержка этих шаблонов была включена в компилятор, но вследствие судебных дел, косвенно затрагивавших фирму Microsoft, библиотека оставалась практически в неизменном состоянии.

Поскольку реализация STL, поставляемая с MSVC4-6, предназначалась для компилятора без поддержки шаблонов функций классов, авторы библиотеки имитировали эти шаблоны и заменили их конкретными функциями, которым при вызове передавались итераторы контейнера соответствующего типа. Например, шаблон insert был заменен следующей функцией:

void insert(iterator position,// "iterator" - тип итератора

iterator first, iterator last): // для конкретного контейнера

Эта ограниченная форма интервальных функций позволяла выполнить интервальную вставку из vector в vector или из list в list, но смешанные операции (например, вставка из vector в list или из set в deque) не поддерживались. Более того, не поддерживалась даже интервальная вставка (а также конструирование или assign) из vector в vector, поскольку итераторы vector:: iterator и vector:: iterator относятся к разным типам. В результате следующий фрагмент, принимаемый другими компиляторами, не компилируется в MSVC4-6:

istream_iterator begin(cin),end;

vector vw(begin.end);

list lw;

lw.assign(vw.rbegin(),vw.rend());// Присвоить lw содержимое vw

// (в обратном порядке);

// не компилируется в MSVC4-6!

SpeciаlContainer scw:

scw.insert(scw.end(),lw.begin(),lw.end()); // Вставить в конец sew

// копию объектов Widget из lw:

// не компилируется в MSVC4-6!

Так что же делать, если вы работаете в среде MSVC4-6? Это зависит от используемой версии MSVC и того, вынуждены ли вы использовать реализацию STL, поставляемую вместе с компилятором.

 

Обходное решение для MSVC4-5

Еще раз посмотрим на правильный код, который не компилируется для реализации STL из поставки MSVC4-6:

vector vw(begin,end);// Отвергается реализацией STL

// из поставки MSVC4-6

list lw;

lw.assign(vw.rbegin(),vw.rend());// То же

SpeciаlContainer scw;

scw.insert(scw.end(),lw.begin(),lw.end()); // То же

// Создать итераторы begin и end

// для чтения объектов Widget

// из cn (см. совет 6).

// Прочитать объекты Widget

// из cin в vw (см. совет 6)

// не компилируется в MSVC4-6!

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

istream_iterator begin(cin).end:

vector vw(begin,end); //Создать vw конструктором

copy(begin,end,back_inserter(vw));//по умолчанию и скопировать

//в него объекты Widget из cin

list lw;

lw.clear(); //Удалить из lw старые объекты:

copy(vw.rbegin(),vw.rend(),//скопировать объекты из vw

back_inserter(lw))://(в обратном порядке)

SpecialContainer scw;

copy(lw.begin().lw.end().// Скопировать объекты Widget

inserter(scw.scw.end()));// из lw в конец sew

Я рекомендую использовать эти обходные решения с библиотекой, входящей в комплект поставки MSVC4-5. С другой стороны, будьте внимательны и не забывайте о том, что эти решения являются обходными. Как показано в совете 5, алгоритм сору почти всегда уступает интервальной функции контейнера, поэтому как только представится возможность обновить платформу STL до версии с поддержкой шаблонов функций класса, откажитесь от использования сору в тех местах, где следовало бы использовать интервальные функции.

 

Обходное решение для MSVC6

Обходное решение из предыдущего раздела подходит и для MSVC6, но в этом случае существует и другой вариант. Компиляторы MSVC4-5 не обладают полноценной поддержкой шаблонов функций класса, поэтому отсутствие этих шаблонов в реализации STL несущественно. В MSVC6 дело обстоит иначе, поскольку компилятор этой среды поддерживает шаблоны функций класса. Таким образом, возникает естественное желание заменить реализацию STL из поставки MSVC6 другой реализацией с шаблонами функций классов, предписанными Стандартом.

В совете 50 упоминаются свободно распространяемые реализации STL от SGI и STLport; в списках поддерживаемых компиляторов обеих реализаций упоминается MSVC6. Кроме того, можно приобрести новейшую MSVC-совместимую реализацию STL от Dinkumware. У каждого из этих вариантов есть свои достоинства и недостатки.

Реализации SGI и STLport распространяются бесплатно, поэтому какая-либо официальная поддержка в этих случаях попросту отсутствует. Более того, поскольку реализации SGI и STLport рассчитаны на работу с разными компиляторами, вам придется дополнительно настроить их для обеспечения максимального быстродействия в MSVC6. В частности, может потребоваться включение поддержки шаблонов функций классов — из-за совместимости с большим количеством разных компиляторов в SGI и/или STLport эта поддержка отключена по умолчанию. Возможно, также придется позаботиться о компоновке с другими библиотеками MSVC6 (особенно DLL), проследить за использованием соответствующих версий для отладки и т. д.

Если подобные вещи вас пугают или вы руководствуетесь принципом «бесплатные программы обходятся слишком дорого», рассмотрите альтернативную реализацию STL для MSVC6 от Dinkumware. Библиотека проектировалась с расчетом на максимальную простоту замены и на соответствие Стандарту. Реализация STL из MSVC6 разрабатывалась именно в Dinkumware, поэтому вполне возможно, что новая реализация STL действительно легко заменяет оригинал. За дополнительной информацией о реализациях STL от Dunkumware обращайтесь на сайт компании .

Независимо от того, на какой реализации вы остановите свой выбор, вы получите нечто большее, чем STL с шаблонами функций классов. В альтернативных реализациях будут решены проблемы соответствия Стандарту в других областях — скажем, отсутствие объявления push_back в контейнере string. Более того, в вашем распоряжении окажутся полезные расширения STL, в том числе хэшированные контейнеры (см. совет 25) и односвязные списки (контейнер slist). Реализации SGI и STLport также содержат множество нестандартных классов функторов, включая select1st и select2nd (см. совет 50).

Но даже если вы вынуждены работать с реализацией STL из поставки MSVC6, сайт Dunkumware все же стоит посетить. На нем перечислены известные ошибки в реализации библиотеки MSVC6 и приведены рекомендации относительно того, как модифицировать библиотеку для сокращения ее недостатков. Не стоит и говорить, что редактирование заголовочных файлов библиотеки — дело весьма рискованное. Если у вас возникнут проблемы, не вините в них меня.