C ++ <algorithm>实现解释</algorithm>

时间:2013-07-15 20:36:41

标签: c++ stl-algorithm

当我想知道如何实现C ++标准库中的算法时,我总是看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;
}

此外:您是否知道可以解释可能的算法实现的网站?

4 个答案:

答案 0 :(得分:20)

将其与天真的实现进行比较:

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

  1. count==00增量first

  2. count==1,其版本的增量为first。以上版本为1。

  3. count==2,其版本的增量为first。以上版本为2。

  4. 一种可能性是处理可解除引用但不可递增的迭代器。至少在STL时代,有一个区别。我不确定输入迭代器今天是否具有此属性。

    Here是一个在使用naive实现时似乎会出现的错误,而Here是一些声称“在迭代器递增时执行实际读取操作,而不是在解除引用的“。

    我还没有找到关于可解除引用的,不可递增的输入迭代器的存在的章节。显然,该标准详细说明了copy_n取消引用输入/输出迭代器的次数,但没有详细说明它增加输入迭代器的次数。

    天真的实现比非天真的实现再增加输入迭代器一次。如果我们有一个单行输入迭代器,它在空间不足的情况下读取++copy_n会在进一步输入时不必要地阻塞,尝试读取输入流末尾的数据。

答案 1 :(得分:13)

这只是一个实现。 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;
}

[稍微有点动手,因为我只在输入迭代器是输入迭代器时提供了实现,对于迭代器是随机访问的情况有不同的实现iterator ]该实现有一个错误,它使输入迭代器比预期增加一倍。

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;
}

答案 2 :(得分:7)

使用朴素实现,您可以增加输入迭代器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类模板。

答案 3 :(得分:1)

由于初步检查

if (count > 0)

我们知道计数&gt; 0,因此该代码的作者认为他不需要再次测试计数,直到他达到值1.记住&#34;对于&#34;在每次迭代开始时执行条件测试,而不是在最后。

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

什么都不打印。

因此代码消除了条件分支,如果Size为1,则无需增加/调整&#34;首先&#34; - 因此它是一个预增量。