【问题标题】:C++ <algorithm> implementation explainedC++ <算法> 实现解释
【发布时间】:2013-07-15 20:36:40
【问题描述】:

当我想知道如何实现 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;
}

另外:您知道解释可能的算法实现的网站吗?

【问题讨论】:

  • 这个可能的实现是基于found in libc++
  • 另外,一般注意事项:仅当功能正确但通常远非最佳时,才提供 cppreference 的可能实现,实现可以用几行代码编写:std::shuffle 正在推动它, std::stable_sort 不会发生。如果您对真正发生的事情感兴趣(例如,当 std::copy 编译为 std::memmove 时),请查看 C++ 标准库(例如 LLVM libc++、GNU libstdc++ 或 what-have-you)中的实际实现
  • @Cubbi:在我问这个问题之前,我查看了 MSVC 的实现。我必须梳理大量的函数调用才能达到实际的实现。因此,我喜欢站点 cppreference 的“仅几行代码”,正如 copy_n 示例所示,这些代码仍然正确。算法实现提供了灵感,如何编写我的一个通用的、基于迭代器的算法。我要感谢您对这个问题的宝贵意见和给出的答案,我认为是您,如何指出 copy_n 的幼稚实现的错误。

标签: c++ stl-algorithm


【解决方案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

  1. count==0,两者都以0 递增first

  2. count==1,他们的版本执行零增量 first。上面的版本做了1.

  3. count==2,他们的版本增加了first。上面的版本做了2。

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

Here 是一个错误,如果您使用幼稚的实现,似乎会发生,Here 是一些文档,声称“实际的读取操作是在迭代器递增时执行的,而不是在取消引用时执行的。”

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

朴素的实现比非朴素的实现多增加输入迭代器一倍。如果我们有一个单通道输入迭代器在空间不足的情况下读取++copy_n 可能会不必要地阻塞进一步的输入,试图读取输入流末尾的数据。

【讨论】:

  • @RanEldan 谁有额外的条件?在哪里?
  • @JimBuck 给出一个示例输入,并计算运行的 if 测试,这显示了额外的 if 测试?我没有看到它。两者都比处理的项目数多做 1 次 if-test。
  • +1,但它可能有助于特别指出幼稚的实现是错误的,因为 copy_n 必须支持输入迭代器
  • @ChristianAmmer:每次调用 postfix operator++ 都需要在执行增量之前创建当前状态的迭代器副本。
【解决方案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;
}

【讨论】:

  • here 所述,上述实现不正确。 (@Cubbi 说了什么,还有更多的话)
  • @Yakk 你应该用那个链接来回答,表明一个更天真的实现在功能上是错误的(由于额外的增量)。
  • @Cubby:正确,这个实现中有一个错误。使用 g++ 4.8 中的最新实现进行了更新
  • MSVC 2012 库对于输入迭代器前向迭代器指向标量的指针(在最后一种情况下调用memmove())。
  • 标准是否说明了关于迭代器如何递增的任何内容?我看不到。
【解决方案3】:

使用简单的实现,您将输入迭代器递增n 倍,而不仅仅是n - 1 倍。这不仅可能效率低下(因为迭代器可以具有任意且任意昂贵的用户定义类型),而且当输入迭代器不支持有意义的“过去的结束”状态时,它也可能完全不受欢迎。

举个简单的例子,考虑从std::cin读取n元素:

#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 &lt; n,执行*(result + i) = *(first + i)

24.2.3/3只有一个注释:

[input-iterator] 算法可以与 istreams 作为输入源一起使用 通过istream_iterator 类模板获取数据。

【讨论】:

  • 增量是否比条件更便宜?
  • @RanEldan:迭代器增量可以是用户定义的操作,因此它可以随心所欲。无论哪种方式,您都在进行n 比较!
  • 一般评论:我目前的理解是,标准对 的状态没有要求,因为它经过了使用该流的输入迭代器的算法。我没有看到任何禁止天真的实现。 Cubbi 上面指向 GCC 错误的链接是“重用”流时意外行为的一个有趣示例(我不清楚这是一个实际错误,而不是一个非常隐蔽的陷阱)。
【解决方案4】:

因为初查

if (count > 0)

我们知道 count > 0,因此该代码的作者认为他不需要再次对 count 进行测试,直到他达到 1 的值。请记住,“for”在每个开始时执行条件测试迭代,而不是结束。

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

不会打印任何内容。

因此,代码消除了条件分支,如果 Size 为 1,则无需“先”递增/调整 - 因此它是预递增。

【讨论】:

    猜你喜欢
    • 2014-03-26
    • 2012-11-13
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-25
    • 1970-01-01
    相关资源
    最近更新 更多