|
|
В начале книги я ввел термин «платформа STL», означающий комбинацию конкретного компилятора и конкретной реализации STL. Различие между компилятором и библиотекой особенно важно при использовании компилятора Microsoft Visual C++ версий 6 и ниже (то есть компилятора, входившего в комплект поставки Microsoft Visual Studio версий 6 и ниже), поскольку компилятор иногда способен на большее, чем прилагаемая реализация STL. В настоящем приложении описаны важные недостатки старых платформ STL от Microsoft и предложены обходные решения, делающие работу на этих платформах значительно более удобной.
Дальнейший материал предназначен для разработчиков, использующих Microsoft Visual C++ (MSVC) версий 4-6. В Visual C++ .NET перечисленные проблемы отсутствуют.
Допустим, у вас есть два вектора объектов Widget
, требуется скопировать объекты Widget
из одного вектора в конец другого. Задача решается легко — достаточно воспользоваться интервальной функцией insert
контейнера vector
:
vector<Widget> vw1, vw2;
…
vw1.insert(vw1.end(), vw2.begin(), vw2.end()); // Присоединить к vw1 копию
// объектов Widget из vw2
Аналогичную операцию можно выполнить с контейнерами vector
и deque
:
vector<Widget> vw;
deque<Widget> dw;
…
vw.insert(vw.end(), dw.begin(), dw.end()); // Присоединить к vw копию
// объектов Widget из dw
Оказывается, эту операцию можно выполнить независимо от того, в каких контейнерах хранятся копируемые объекты. Подходят даже нестандартные контейнеры:
vector<Widget> vw;
…
list<Widget> lw;
…
vw.insert(vw.begin(), lw.begin(), lw.end()); // Присоединить к vw копию
// объектов Widget из lw
set<Widget> sw;
…
vw.insert(vw.begin(), sw.begin(), sw.end()); // Присоединить к vw копию
// объектов Widget из sw
template<typename T,
typename Allocator = allocator<T> > // Шаблон нестандартного
class SpecialContainer{…}; // STL-совместимого контейнера
SpecialContainer<Widget> scw;
vw.insert(vw.begin(), scw.begin(), scw.end()); // Присоединить к vw копию
// объектов Widget из scw
Подобная универсальность объясняется тем, что интервальная функция insert
контейнера range
вообще не является функцией в общепринятом смысле. Это шаблон функции контейнера, специализация которого с произвольным типом итератора порождает конкретную интервальную функцию insert
. Для контейнера vector
шаблон insert
объявлен в Стандарте следующим образом:
template <class T, class Allocator = allocator<T> >
class vector {
public:
…
template<class InputIterator>
void insert(iterator position, InputIterator first, InputIterator last);
};
Каждый стандартный контейнер должен поддерживать шаблонную версию интервальной функции insert
. Аналогичные шаблоны также обязательны для интервальных конструкторов и для интервальной формы assign
(см. совет 5).
К сожалению, в реализации STL, входящей в комплект поставки версий 4-6, шаблоны функций не объявляются. Библиотека изначально разрабатывалась для MSVC версии 4, а этот компилятор, как и большинство компиляторов того времени, не обладал поддержкой шаблонов функций классов. При переходе от MSCV4 к MSVC6 поддержка этих шаблонов была включена в компилятор, но вследствие судебных дел, косвенно затрагивавших фирму Microsoft, библиотека оставалась практически в неизменном состоянии.
Поскольку реализация STL, поставляемая с MSVC4-6, предназначалась для компилятора без поддержки шаблонов функций классов, авторы библиотеки имитировали эти шаблоны и заменили их конкретными функциями, которым при вызове передавались итераторы контейнера соответствующего типа. Например, шаблон insert
был заменен следующей функцией:
void insert(iterator position, // "iterator" - тип итератора
iterator first, iterator last); // для конкретного контейнера
Эта ограниченная форма интервальных функций позволяла выполнить интервальную вставку из vector<Widget>
в vector<Widget>
или из list<int>
в list<int>
, но смешанные операции (например, вставка из vector<Widget>
в list<Widget>
или из set<int>
в deque<int>
) не поддерживались. Более того, не поддерживалась даже интервальная вставка (а также конструирование или assign
) из vector<long>
в vector<int>
, поскольку итераторы vector<long>::iterator
и vector<int>::iterator
относятся к разным типам. В результате следующий фрагмент, принимаемый другими компиляторами, не компилируется в MSVC4-6:
istream_iterator<Widget> begin(cin), end; // Создать итераторы begin и end
// для чтения объектов Widget
// из cin (см. совет 6).
vector<Widget> vw(begin, end); // Прочитать объекты Widget
// из cin в vw (см. совет 6)
// не компилируется в MSVC4-6!
list<Widget> lw;
lw.assign(vw.rbegin(), vw.rend()); // Присвоить lw содержимое vw
// (в обратном порядке);
// не компилируется в MSVC4-6!
SpeciаlContainer<Widget> scw;
scw.insert(scw.end(), lw.begin(), lw.end()); // Вставить в конец scw
// копию объектов Widget из lw;
// не компилируется в MSVC4-6!
Так что же делать, если вы работаете в среде MSVC4-6? Это зависит от используемой версии MSVC и того, вынуждены ли вы использовать реализацию STL, поставляемую вместе с компилятором.
Еще раз посмотрим на правильный код, который не компилируется для реализации STL из поставки MSVC4-6:
vector<Widget> vw(begin, end); // Отвергается реализацией STL
// из поставки MSVC4-6
list<Widget> lw;
…
lw.assign(vw.rbegin(), vw.rend()); // То же
SpeciаlContainer<Widget> scw;
…
scw.insert(scw.end(), lw.begin(), lw.end()); // То же
Несмотря на внешние различия, выделенные вызовы отвергаются компилятором по одной и той же причине: из-за отсутствия шаблонов функций класса в реализации STL. Соответственно и решение во всех случаях оказывается одним и тем же: замена вызовом copy
с итератором вставки (см. совет 30). Ниже приведены обходные решения для всех примеров, приведенных ранее:
istream_iterator<Widget> begin(cin), end;
vector<Widget> vw(begin, end); //Создать vw конструктором
copy(begin, end, back_inserter(vw)); // по умолчанию и скопировать
// в него объекты Widget из cin
list<Widget> lw;
…
lw.clear(); // Удалить из lw старые объекты;
copy(vw.rbegin(), vw.rend(), // скопировать объекты из vw
back_inserter(lw)); //(в обратном порядке)
SpecialContainer<Widget> scw;
copy(lw.begin(), lw.end(), // Скопировать объекты Widget
inserter(scw, scw.end())); // из lw в конец scw
Я рекомендую использовать эти обходные решения с библиотекой, входящей в комплект поставки MSVC4-5. С другой стороны, будьте внимательны и не забывайте о том, что эти решения являются обходными. Как показано в совете 5, алгоритм copy
почти всегда уступает интервальной функции контейнера, поэтому как только представится возможность обновить платформу STL до версии с поддержкой шаблонов функций класса, откажитесь от использования copy
в тех местах, где следовало бы использовать интервальные функции.
Обходное решение из предыдущего раздела подходит и для 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 обращайтесь на сайт компании http://www.dunkumware.com.
Независимо от того, на какой реализации вы остановите свой выбор, вы получите нечто большее, чем STL с шаблонами функций классов. В альтернативных реализациях будут решены проблемы соответствия Стандарту в других областях — скажем, отсутствие объявления push_back
в контейнере string
. Более того, в вашем распоряжении окажутся полезные расширения STL, в том числе хэшированные контейнеры (см. совет 25) и односвязные списки (контейнер slist
). Реализации SGI и STLport также содержат множество нестандартных классов функторов, включая select1st
и select2nd
(см. совет 50).
Но даже если вы вынуждены работать с реализацией STL из поставки MSVC6, сайт Dunkumware все же стоит посетить. На нем перечислены известные ошибки в реализации библиотеки MSVC6 и приведены рекомендации относительно того, как модифицировать библиотеку для сокращения ее недостатков. Не стоит и говорить, что редактирование заголовочных файлов библиотеки — дело весьма рискованное. Если у вас возникнут проблемы, не вините в них меня.