81

算法解决方案:

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

基于范围的for循环解决方案:

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

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

4

10 回答 10

79

第一个版本

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

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

在第二个版本中,读者必须自己弄清楚。

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

于 2013-01-10T13:23:37.637 回答
42

for 循环是否基于范围根本没有区别,它只是简化了括号内的代码。算法更清晰,因为它们显示了意图

于 2013-01-10T13:15:50.130 回答
30

就个人而言,我最初的阅读:

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

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

我的初步阅读:

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

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

这些非常相似,但并不完全相同。我可能想引起第一次阅读的一个合理原因是,因为我认为有关此代码的最重要的事实是它分配给范围。所以这就是你的“我为什么要……”。我使用generate因为在 C++ 中std::generate意味着“范围分配”。正如 btw 所做的那样std::copy,两者之间的区别是您分配的内容。

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

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

我认为,最简单的算法与等效循环相比只有一点点优势。基于范围的 for 循环改进了循环(主要是通过删除大部分样板,尽管它们还有更多的东西)。因此,利润越来越紧,也许您在某些特定情况下会改变主意。但那里仍然存在风格差异。

于 2013-01-10T14:06:53.337 回答
23

在我看来,Effective STL Item 43:“Prefer algorithm calls to hand-written loops”。仍然是一个很好的建议。

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

my_util::generate(numbers, rand);

我相信它在传达意图和可读性方面都优于基于范围的 for 循环。


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

考虑以下来自Herb Sutter 的示例:Lambdas, Lambdas Everywhere

任务:找到 v 中的第一个元素,即> x< y

没有 lambda:

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; } );
于 2013-01-10T13:34:48.960 回答
22

看来,手动循环虽然可能会减少冗长,但缺乏可读性:

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

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

当您使用std::generate.

1.在这个上下文中初始化意味着给容器的元素赋予有意义的值。

于 2013-01-10T13:20:20.043 回答
9

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

用来自一个分布的变量填充容器limit(排除,limit是一个有效的迭代器numbers),其余的用来自另一个分布的变量。

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

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

于 2013-01-10T16:24:13.023 回答
6

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

也就是说,我还有另一个理由不丢弃 std::algorithm - 某些算法专门用于某些数据类型。

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

所以我想在这里打一张效率牌。

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

于 2013-01-11T06:27:18.823 回答
3

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

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

但是使用基于范围的 for 要好得多:

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

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

std::for_each(c.range(), [](x)
{
    // do stuff with x
});
于 2013-01-10T13:40:59.340 回答
1

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

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

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

于 2013-01-11T13:54:09.320 回答
1

基于范围的 for 循环就是这样。直到当然标准被改变。

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

于 2013-01-11T22:26:25.963 回答