Подтвердить что ты не робот

Реализация С++ <algorithm>

Когда мне нравится знать, как можно реализовать алгоритм в стандартной библиотеке С++, я всегда смотрю http://en.cppreference.com/w/cpp/algorithm, что является отличный источник. Но иногда я не понимаю детали реализации, и мне нужно объяснение, почему что-то сделано именно так. Например, при реализации std::copy_n, почему первое назначение выполняется вне цикла, и поэтому цикл начинается с 1?

template< class InputIt, class Size, class OutputIt>
OutputIt copy_n(InputIt first, Size count, OutputIt result)
{
    if (count > 0) {
        *result++ = *first;
        for (Size i = 1; i < count; ++i) {
            *result++ = *++first;
        }
    }
    return result;
}

Дополнительно: знаете ли вы сайт, на котором объясняются возможные варианты реализации алгоритма?

4b9b3361

Ответ 1

Сравните это с наивной реализацией:

template< class InputIt, class Size, class OutputIt>
OutputIt copy_n(InputIt first, Size count, OutputIt result)
{
  for (Size i = 0; i < count; ++i) {
    *result++ = *first++;
  }
  return result;
}

Эта версия делает еще одно приращение first!

  • count==0, оба делают 0 приращения first.

  • count==1, их версия имеет нулевые приращения first. Вышеприведенная версия делает 1.

  • count==2, их версия делает один шаг first. В приведенной выше версии 2.

Возможность состоит в том, чтобы обрабатывать итераторы, которые являются разыскаемыми, но не увеличиваются. По крайней мере, в STL-дни было различие. Я не уверен, что итераторы ввода имеют это свойство сегодня.

Здесь - ошибка, которая возникает, если вы используете наивную реализацию, а Здесь документация, которая утверждает: "Фактическая операция чтения выполняется, когда итератор увеличивается, а не когда он разыменован".

Я еще не проследил главу и стих о существовании разыменованных и неинкрементных входных итераторов. По-видимому, стандартная информация о том, сколько раз copy_n разыгрывает итераторы ввода/вывода, но не указывает, сколько раз она увеличивает входной итератор.

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

Ответ 2

Это просто реализация. Реализация в GCC 4.4 отличается (и концептуально проще):

template<typename InputIterator, typename _Size, typename _OutputIterator>
_OutputIterator
copy_n(_InputIterator __first, _Size __n,
     _OutputIterator __result)
{
  for (; __n > 0; --__n)
{
  *__result = *__first;
  ++__first;
  ++__result;
}
  return __result;
}

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

Реализация в GCC 4.8 немного запутана:

template<typename _InputIterator, typename _Size, typename _OutputIterator>
_OutputIterator
copy_n(_InputIterator __first, _Size __n,
     _OutputIterator __result)
{
  if (__n > 0)
{
  while (true)
    {
      *__result = *__first;
      ++__result;
      if (--__n > 0)
    ++__first;
      else
    break;
    }
}
  return __result;
}

Ответ 3

При наивной реализации вы увеличиваете входной итератор n раз, а не только n - 1 раз. Это не просто потенциально неэффективно (поскольку итераторы могут иметь произвольные и произвольно дорогие пользовательские типы), но это также может быть совершенно нежелательным, если входной итератор не поддерживает значимое "прошлое" состояние.

Для простого примера рассмотрим возможность чтения элементов n из std::cin:

#include <iostream>    // for std:cin
#include <iterator>    // for std::istream_iterator


std::istream_iterator it(std::cin);
int dst[3];

При наивном решении программа блокирует окончательное приращение:

int * p = dst;

for (unsigned int i = 0; i != 3; ++i) { *p++ = *it++; }   // blocks!

Стандартный алгоритм библиотеки не блокируется:

#include <algorithm>

std::copy_n(it, 3, dst);    // fine

Обратите внимание, что стандарт фактически не говорит об увеличении итератора. Он только говорит (25.3.1/5), что copy_n(first, n, result) имеет

Эффекты: для каждого неотрицательного целого i < n выполняется *(result + i) = *(first + i).

В 24.2.3/3 есть только примечание:

[input-iterator] алгоритмы могут использоваться с istreams в качестве источника ввода данных через шаблон класса istream_iterator.

Ответ 4

Из-за начальной проверки

if (count > 0)

мы знаем, что count > 0, поэтому автор этого кода чувствовал, что ему не нужно было снова проверять счетчик, пока не достигнет значения 1. Помните, что "для" выполняет условный тест в начале каждого итерации, а не в конце.

Size count = 1;
for (Size i = 1; i < count; ++i) {
    std::cout << i << std::endl;
}

ничего не печатает.

Таким образом, код исключает условную ветвь, и если Size равен 1, это исключает необходимость увеличения/настройки "первым" - следовательно, это предварительный приращение.