基于范围的'for'循环是否弃用了许多简单的算法?

时间:2013-01-10 13:13:47

标签: c++ algorithm stl c++11 foreach

算法解决方案:

std::generate(numbers.begin(), numbers.end(), rand);

基于范围的for-loop解决方案:

for (int& x : numbers) x = rand();

为什么我要在C ++ 11中使用更冗长的std::generate基于范围的for循环?

10 个答案:

答案 0 :(得分:78)

第一个版本

std::generate(numbers.begin(), numbers.end(), rand);

告诉我们您要生成一系列值。

在第二个版本中,读者必须自己解决这个问题。

节省打字通常不是最理想的,因为它通常在阅读时间中丢失。大多数代码的读取次数比输入的多得多。

答案 1 :(得分:42)

for循环是否基于范围并没有产生任何影响,它只是简化了括号内的代码。算法更清晰,因为它们显示 intent

答案 2 :(得分:30)

就个人而言,我的初步读物是:

std::generate(numbers.begin(), numbers.end(), rand);

是“我们正在分配范围内的所有内容。范围为numbers。分配的值是随机的。”

我的初步读物:

for (int& x : numbers) x = rand();

是“我们正在对范围内的所有事情做一些事情。范围是numbers。我们所做的是分配一个随机值。”

这些非常类似,但不完全相同。我可能想要引起一读的一个合理的原因是因为我认为关于这个代码的最重要的事实是它分配给范围。所以,你的“我为什么要...”。我使用generate因为在C ++中std::generate表示“范围分配”。正如顺便说一句std::copy,两者之间的差异就是你所分配的。

但是,有一些令人困惑的因素。与基于迭代器的算法相比,基于范围的for循环具有表达范围numbers的固有更直接的方式。这就是人们使用基于范围的算法库的原因:boost::range::generate(numbers, rand);看起来比std::generate版更好。

与此相反,基于范围的for循环中的int&是一个皱纹。如果范围的值类型不是int,那么我们在这里做一些非常微妙的事情取决于它可以转换为int&,而generate代码仅依赖于从rand返回可返回元素的返回。即使值类型为int,我仍然可能会停下来思考它是否存在。因此auto推迟了对类型的思考,直到我看到被分配的内容 - 用auto &x我说“引用范围元素,无论可能有什么类型”。回到C ++ 03,算法(因为它们是函数模板)是隐藏确切类型的 方法,现在它们是 方式。

我认为最简单的算法一直是等效循环的边际优势。基于范围的for循环改善了循环(主要是通过删除大部分样板,尽管它们比它更多)。所以利润率越来越紧,也许你会在某些特定情况下改变主意。但那里仍然存在风格差异。

答案 3 :(得分:23)

在我看来,有效STL第43项:“首选算法调用手写循环”。仍然是一个很好的建议。

我通常会编写包装函数来摆脱begin() / end()地狱。如果你这样做,你的例子将如下所示:

my_util::generate(numbers, rand);

我认为它在传达意图和可读性方面优于循环范围。


话虽如此,我必须承认,在C ++ 98中,一些STL算法调用产生了无法解释的代码,并且遵循“Prefer算法调用手写循环”似乎不是一个好主意。幸运的是,lambdas改变了这一点。

Herb Sutter: Lambdas, Lambdas Everywhere考虑​​以下示例。

任务:在v中查找> x< y的第一个元素。

没有lambdas:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

使用lambda

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

答案 4 :(得分:22)

我的意见中,手动循环虽然可能会减少冗长,但缺乏准确性:

for (int& x : numbers) x = rand();

我不会使用这个循环初始化 1 numbers 定义的范围,因为当我看到它时,在我看来它是迭代一系列数字,但实际上它并不(实质上),即从该范围中读取而不是写入到该范围。

使用std::generate时,意图会更清晰。

1。在此上下文中初始化意味着为容器的元素提供有意义的值。

答案 5 :(得分:9)

有些事情你不能(简单地)使用基于范围的循环,算法将迭代器作为输入。 例如std::generate

将容器填充到limit(排除,limitnumbers上的有效迭代器),其中包含来自一个分布的变量,其余来自另一个分布中的变量。

std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);

基于迭代器的算法可以更好地控制您操作的范围。

答案 6 :(得分:6)

对于std::generate的特定情况,我同意先前关于可读性/意图问题的答案。 std :: generate对我来说似乎是一个更清晰的版本。但我承认这是一种品味问题。

那就是说,我还有另外一个理由不抛弃std :: algorithm - 某些算法专门针对某些数据类型。

最简单的例子是std::fill。通用版本在提供的范围内实现为for循环,并且在实例化模板时将使用此版本。但不总是。例如。如果你为它提供一个std::vector<int>的范围 - 它通常会在引擎盖下调用memset,从而产生更快更好的代码。

所以我在这里尝试效率卡。

您的手写循环可能与std :: algorithm版本一样快,但它几乎不会更快。除此之外,std :: algorithm可能专门用于特定的容器和类型,它是在干净的STL接口下完成的。

答案 7 :(得分:3)

我的答案可能是也许没有。如果我们谈论C ++ 11,那么也许(更像是没有)。例如std::for_each使用lambdas非常烦人:

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

但使用基于范围的更好:

for (auto& x : c)
{
    // do stuff with x
}

另一方面,如果我们谈论C ++ 1y,那么我认为不会,算法不会被基于范围的算法淘汰。在C ++标准委员会中,有一个研究小组正在研究向C ++添加范围的提议,并且还有关于多态lambda的工作。范围将消除使用迭代器对的需要,并且多态lambda将允许您不指定lambda的确切参数类型。这意味着std::for_each可以像这样使用(不要把它当作一个事实,这就是今天梦想的样子):

std::for_each(c.range(), [](x)
{
    // do stuff with x
});

答案 8 :(得分:1)

应该注意的一件事是,算法表达的是什么,而不是如何。

基于范围的循环包括事情的完成方式:从第一个开始,应用并转到下一个元素直到结束。即使是一个简单的算法也可以做不同的事情(至少对特定容器有一些重载,甚至不考虑可怕的向量),至少它的完成方式不是作家业务。

对我而言,差异很大,尽可能地封装,并且尽可能证明句子的合理性,使用算法。

答案 9 :(得分:1)

基于范围的for循环就是这样。直到标准改变为止。

算法是一个函数。一个对其参数提出一些要求的函数。这些要求在标准中表达,以允许例如利用所有可用执行线程的实现,并将自动加速。