KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Скотт Мейерс - Эффективное использование STL

Скотт Мейерс - Эффективное использование STL

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Скотт Мейерс, "Эффективное использование STL" бесплатно, без регистрации.
Перейти на страницу:

Чтобы вызов нормально компилировался, необходимо ликвидировать неоднозначность. Для этого проще всего явно задать параметр-тип, используемый distance, и избавить компилятор от необходимости определять его самостоятельно:

advanced.distance<ConstIter>(i,ci)): // Вычислить расстояние между

// i и ci (как двумя const_iterator)

// и переместить i на это расстояние

Итак, теперь вы знаете, как при помощи advance и distance получить iterator, соответствующий заданному const_iterator, но до настоящего момента совершенно не рассматривался вопрос, представляющий большой практический интерес: насколько эффективна данная методика? Ответ прост: она эффективна настолько, насколько это позволяют итераторы. Для итераторов произвольного доступа, поддерживаемых контейнерами vector, string, deque и т. д., эта операция выполняется с постоянным временем. Для двусторонних итераторов (к этой категории относятся итераторы других стандартных контейнеров, а также некоторых реализаций хэшированных контейнеров — см. совет 25) эта операция выполняется с линейным временем.

Поскольку получение iterator, эквивалентного const_iterator, может потребовать линейного времени, и поскольку это вообще невозможно сделать при недоступности контейнера, к которому относится const_iterator, проанализируйте архитектурные решения, вследствие которых возникла необходимость получения iterator по const_iterator. Результат такого анализа станет дополнительным доводом в пользу совета 26, рекомендующего отдавать предпочтение iterator перед const- и reverse-итераторами.

Совет 28. Научитесь использовать функцию base

При вызове функции base для итератора reverse_iterator будет получен «соответствующий» iterator, однако из сказанного совершенно не ясно, что же при этом происходит. В качестве примера рассмотрим следующий фрагмент, который заносит в вектор числа 1-5, устанавливает reverse_iterator на элемент 3 и инициализирует iterator функцией base:

vector<int> v;

v.reserve(5);

//См. совет 14

for (int i=1;i<=5;++i){

v.push_back(i);

// Занести в вектор числа 1-5

vector<int>::reverse_iterator ri=

find(v.rbegin(),v.rend(),3);

vector<int>:: iterator i (ri.base());

// Установить ri на элемент 3

// Присвоить i результат вызова base

// для итератора ri

После выполнения этого фрагмента ситуация выглядит примерно так:


На рисунке видно характерное смещение reverse_iterator и соответствующего базового итератора, воспроизводящего смещение begin() и end() по отношению к begin() и end(), но найти на нем ответы на некоторые вопросы не удается. В частности, рисунок не объясняет, как использовать i для выполнения операций, которые должны были выполняться с ri.

Как упоминалось в совете 26, некоторые функции контейнеров принимают в качестве параметров-итераторов только iterator. Поэтому если вы, допустим, захотите вставить новый элемент в позицию, определяемую итератором п, сделать это напрямую не удастся; функция insert контейнера vector не принимает reverse_ iterator. Аналогичная проблема возникает при удалении элемента, определяемого итератором г . Функции erase не соглашаются на reverse_iterator и принимают только iterator. Чтобы выполнить удаление или вставку, необходимо преобразовать reverse_iterator в iterator при помощи base, а затем воспользоваться iterator для выполнения нужной операции.

Допустим, потребовалось вставить в v новый элемент в позиции, определяемой итератором n. Для определенности будем считать, что вставляется число 99. Учитывая, что n на предыдущем рисунке используется для перебора справа налево, а новый элемент вставляется перед позицией итератора, определяющего позицию вставки, можно ожидать, что число 99 окажется перед числом 3 в обратном порядке перебора. Таким образом, после вставки вектор v будет выглядеть так:


Конечно, мы не можем использовать n для обозначения позиции вставки, поскольку это не iterator. Вместо этого необходимо использовать i. Как упоминалось выше, когда n указывает на элемент 3, i (то есть r. base()) указывает на элемент 4. Именно на эту позицию должен указывать итератор i, чтобы вставленный элемент оказался в той позиции, в которой он бы находился, если бы для вставки можно было использовать итератор r.

Заключение:

• чтобы эмулировать вставку в позицию, заданную итератором ri типа reverse_ iterator, выполните вставку в позицию r.base(). По отношению к операции вставки ri и r.base() эквивалентны, но r.base() в действительности представляет собой iterator, соответствующий ri.

Рассмотрим операцию удаления элемента. Вернемся к взаимосвязи между ri и исходным вектором (по состоянию на момент, предшествующий вставке значения 99):


Для удаления элемента, на который указывает итератор r, нельзя просто использовать , поскольку этот итератор ссылается на другой элемент. Вместо этого нужно удалить элемент, предшествующий i.

Заключение:

• чтобы эмулировать удаление в позиции, заданной итератором ri типа reverse_ iterator, выполните удаление в позиции, предшествующей ri .base(). По отношению к операции удаления ri и ri .base() не эквивалентны, a ri .base() не является объектом iterator, соответствующим ri.

Однако к коду стоит присмотреться повнимательнее, поскольку вас ждет сюрприз:

vector<int> v;

… // См. ранее. В вектор v заносятся // числа 1-5

vector<int>::reverse_iterator ri = // Установить ri на элемент 3

find(v.rbegin(),v.rend(),3);

v.erase(--ri .base());// Попытка стирания в позиции.

// предшествующей ri-base():

// для вектора обычно

// не компилируется

Решение выглядит вполне нормально. Выражение --ri.base() правильно определяет элемент, предшествующий удаляемому. Более того, приведенный фрагмент будет нормально работать для всех стандартных контейнеров, за исключением vector и string. Наверное, он бы мог работать и для этих контейнеров, но во многих реализациях vector и string он не будет компилироваться. В таких реализациях типы iterator (и const_iterator) реализованы в виде встроенных указателей, поэтому результатом вызова i.base() является указатель. В соответствии с требованиями как С, так и С++ указатели, возвращаемые функциями, не могут модифицироваться, поэтому на таких платформах STL выражения типа --i.base() не компилируются. Чтобы удалить элемент в позиции, заданной итератором reverse_iterator, и при этом сохранить переносимость, необходимо избегать модификации возвращаемого значения base. Впрочем, это несложно. Если мы не можем уменьшить результат вызова base, значит, нужно увеличить reverse_iterator и после этого вызвать base!

//См. ранее

v.erase((++ri).base()); // Удалить элемент, на который указывает ri;

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

Такая методика работает во всех стандартных контейнерах и потому считается предпочтительным способом удаления элементов, определяемых итератором reverse_iterator.

Вероятно, вы уже поняли: говорить о том, что функция base класса reverse_ iterator возвращает «соответствующий» iterator, не совсем правильно. В отношении вставки это действительно так, а в отношении удаления — нет. При преобразовании reverse_iterator в iterator важно знать, какие операции будут выполняться с полученным объектом iterator. Только в этом случае вы сможете определить, подойдет ли он для ваших целей.

Совет 29. Рассмотрите возможность использования istreambuf_iterator при посимвольном вводе

Предположим, вы хотите скопировать текстовый файл в объект string. На первый взгляд следующее решение выглядит вполне разумно:

ifstream inputFile("interestringData.txt");

string fileData(istream_iterator<char>(inputFile)), // Прочитать inputFile istream iterator<char>0);// в fileData

Но вскоре выясняется, что приведенный синтаксис не копирует в строку пропуски (whitespace), входящие в файл. Это объясняется тем, что isream_iterator производит непосредственное чтение функциями operator<<, а эти функции по умолчанию не читают пропуски.

Чтобы сохранить пропуски, входящие в файл, достаточно включить режим чтения пропусков сбросом флага skipws для входного потока:

ifstream inputFile("interestingData.txt"):

inputFle.unset(ios::skipws);// Включить режим

// чтения пропусков

// в inputFile

string fileData(istream_iterator<char>(inputFile)), // Прочитать inputFile istream_iterator<char>0);// в fileData.

Теперь все символы InputFile копируются в fileData.

Кроме того, может выясниться, что копирование происходит не так быстро, как вам хотелось бы. Функции operator<<, от которых зависит работа stream_iterator, производят форматный ввод, а это означает, что каждый вызов сопровождается многочисленными служебными операциями. Они должны создать и уничтожить объекты sentry (специальные объекты потоков ввода-вывода, выполняющие начальные и завершающие операции при каждом вызове operator<<); они должны проверить состояние флагов, влияющих на их работу (таких, как skpws); они должны выполнить доскональную проверку ошибок чтения, а в случае обнаружения каких-либо проблем — проанализировать маску исключений потока и определить, нужно ли инициировать исключение. Все перечисленные операции действительно важны при форматном вводе, но если ваши потребности ограничиваются чтением следующего символа из входного потока, без них можно обойтись.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*