人们应该更喜欢 STL 算法而不是手工循环吗?

发布于 2024-07-06 07:45:17 字数 288 浏览 6 评论 0原文

我似乎在问题和迭代器上看到了更多的“for”循环。 这里的答案比我对 for_each()、transform() 等的答案更好。 Scott Meyers 建议stl 算法是首选,或者至少他在 2001 年是这样做的。当然,使用它们通常意味着将循环体移动到函数或函数对象中。 有些人可能觉得这是一个不可接受的并发症,而另一些人可能觉得这样可以更好地解决问题。

那么……STL 算法应该优于手工循环吗?

I seem to be seeing more 'for' loops over iterators in questions & answers here than I do for_each(), transform(), and the like. Scott Meyers suggests that stl algorithms are preferred, or at least he did in 2001. Of course, using them often means moving the loop body into a function or function object. Some may feel this is an unacceptable complication, while others may feel it better breaks down the problem.

So... should STL algorithms be preferred over hand-rolled loops?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

十二 2024-07-13 07:45:17

这取决于:

  • 是否需要高性能
  • 循环的可读性
  • 算法是否复杂

如果循环不是瓶颈,并且算法很简单(如 for_each),那么对于当前的 C++ 标准,我更喜欢手卷循环以提高可读性。 (逻辑的局部性是关键。)

但是,既然 C++0x/C++11 得到了一些主要编译器的支持,我会说使用 STL 算法,因为它们现在允许 lambda 表达式 - 从而允许逻辑的局部性。

It depends on:

  • Whether high-performance is required
  • The readability of the loop
  • Whether the algorithm is complex

If the loop isn't the bottleneck, and the algorithm is simple (like for_each), then for the current C++ standard, I'd prefer a hand-rolled loop for readability. (Locality of logic is key.)

However, now that C++0x/C++11 is supported by some major compilers, I'd say use STL algorithms because they now allow lambda expressions — and thus the locality of the logic.

故人如初 2024-07-13 07:45:17

我在这里要反其道而行之,主张使用带有函子的 STL 算法可以使代码更容易理解和维护,但你必须做得正确。 您必须更加注意可读性和清晰度。 特别是,你必须正确命名。 但当你这样做时,你最终可以得到更干净、更清晰的代码,并将范式转变为更强大的编码技术。

让我们举个例子。 这里我们有一群孩子,我们想将他们的“Foo Count”设置为某个值。 标准的 for 循环、迭代器方法是:

for (vector<Child>::iterator iter = children.begin();
    iter != children.end();
    ++iter)
{
    iter->setFooCount(n);
}

是的,它非常清楚,而且绝对不是代码。 只要稍微看一下就可以弄清楚。 但是看看我们可以用适当的函子做什么:

for_each(children.begin(), children.end(), SetFooCount(n));

哇,这正是我们所需要的。 你不必弄清楚; 您立即知道它正在设置每个孩子的“Foo Count”。 (如果我们不需要 .begin() / .end() 废话,那就更清楚了,但你不可能拥有一切,而且他们在制作 STL 时没有咨询我。)

当然,你确实需要定义这个神奇的函子,SetFooCount,但它的定义非常样板:

class SetFooCount
{
public:
    SetFooCount(int n) : fooCount(n) {}

    void operator () (Child& child)
    {
        child.setFooCount(fooCount);
    }

private:
    int fooCount;
};

总的来说,它的代码更多,你必须看看另一个地方才能准确地找出 SetFooCount 是什么正在做。 但因为我们命名得好,所以 99% 的时候我们都不必查看 SetFooCount 的代码。 我们假设它按照它所说的进行操作,并且我们只需要查看 for_each 行。

我真正喜欢的是使用算法会带来范式转变。 您不必将列表视为对象的集合,并对列表中的每个元素进行操作,而是将列表视为第一类实体,并直接对列表本身进行操作。 for 循环遍历列表,对每个元素调用成员函数来设置 Foo Count。 相反,我正在执行一个命令,该命令设置列表中每个元素的 Foo Count。 这很微妙,但是当你只看森林而不是树木时,你会获得更多的力量。

因此,只要稍加思考和仔细命名,我们就可以使用 STL 算法来编写更清晰、更清晰的代码,并开始在更细粒度的层面上进行思考。

I’m going to go against the grain here and advocate that using STL algorithms with functors makes code much easier to understand and maintain, but you have to do it right. You have to pay more attention to readability and clearity. Particularly, you have to get the naming right. But when you do, you can end up with cleaner, clearer code, and paradigm shift into more powerful coding techniques.

Let’s take an example. Here we have a group of children, and we want to set their “Foo Count” to some value. The standard for-loop, iterator approach is:

for (vector<Child>::iterator iter = children.begin();
    iter != children.end();
    ++iter)
{
    iter->setFooCount(n);
}

Which, yeah, it’s pretty clear, and definitely not bad code. You can figure it out with just a little bit of looking at it. But look at what we can do with an appropriate functor:

for_each(children.begin(), children.end(), SetFooCount(n));

Wow, that says exactly what we need. You don’t have to figure it out; you immediately know that it’s setting the “Foo Count” of every child. (It would be even clearer if we didn’t need the .begin() / .end() nonsense, but you can’t have everything, and they didn’t consult me when making the STL.)

Granted, you do need to define this magical functor, SetFooCount, but its definition is pretty boilerplate:

class SetFooCount
{
public:
    SetFooCount(int n) : fooCount(n) {}

    void operator () (Child& child)
    {
        child.setFooCount(fooCount);
    }

private:
    int fooCount;
};

In total it’s more code, and you have to look at another place to find out exactly what SetFooCount is doing. But because we named it well, 99% of the time we don’t have to look at the code for SetFooCount. We assume it does what it says, and we only have to look at the for_each line.

What I really like is that using the algorithms leads to a paradigm shift. Instead of thinking of a list as a collection of objects, and doing things to every element of the list, you think of the list as a first class entity, and you operate directly on the list itself. The for-loop iterates through the list, calling a member function on each element to set the Foo Count. Instead, I am doing one command, which sets the Foo Count of every element in the list. It’s subtle, but when you look at the forest instead of the trees, you gain more power.

So with a little thought and careful naming, we can use the STL algorithms to make cleaner, clearer code, and start thinking on a less granular level.

你好,陌生人 2024-07-13 07:45:17

std::foreach 是几年前让我诅咒 STL 的代码。

我不能说它是否更好,但我更喜欢将循环代码放在循环前导码下。 对我来说,这是一个强烈的要求。 而 std::foreach 构造不允许我这样做(奇怪的是,就我而言,Java 或 C# 的 foreach 版本很酷......所以我想它证实了这一点对我来说,循环体的局部性非常非常重要)。

因此,只有当已经有一个可读/可理解的算法可用时,我才会使用 foreach 。 如果没有,不,我不会。 但这是一个品味问题,我想,因为我也许应该更加努力地理解和学习解析所有这些东西......

请注意,boost 的人显然有同样的感觉,因为他们写了 BOOST_FOREACH:

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

参见:< a href="http://www.boost.org/doc/libs/1_35_0/doc/html/foreach.html" rel="noreferrer">http://www.boost.org/doc/libs/1_35_0/ doc/html/foreach.html

The std::foreach is the kind of code that made me curse the STL, years ago.

I cannot say if it's better, but I like more to have the code of my loop under the loop preamble. For me, it is a strong requirement. And the std::foreach construct won't allow me that (strangely enough, the foreach versions of Java or C# are cool, as far as I am concerned... So I guess it confirms that for me the locality of the loop body is very very important).

So I'll use the foreach only if there is only already a readable/understandable algorithm usable with it. If not, no, I won't. But this is a matter of taste, I guess, as I should perhaps try harder to understand and learn to parse all this thing...

Note that the people at boost apparently felt somewhat the same way, for they wrote BOOST_FOREACH:

#include <string>
#include <iostream>
#include <boost/foreach.hpp>

int main()
{
    std::string hello( "Hello, world!" );

    BOOST_FOREACH( char ch, hello )
    {
        std::cout << ch;
    }

    return 0;
}

See : http://www.boost.org/doc/libs/1_35_0/doc/html/foreach.html

誰ツ都不明白 2024-07-13 07:45:17

这确实是斯科特·迈耶斯犯错的一件事。

如果有一个实际的算法符合您需要做的事情,那么当然使用该算法。

但是,如果您需要做的只是循环遍历集合并对每个项目执行某些操作,则只需执行正常的循环即可,而不是尝试将代码分离到不同的函子中,这最终只会将代码切成小块,而没有任何实际收益。

还有一些其他选项,例如 boost::bind 或 boost::lambda,但这些确实是复杂的模板元编程事物,它们在调试和单步执行代码方面效果不佳,因此通常应该避免使用它们。

正如其他人提到的,当 lambda 表达式成为一等公民时,这一切都会改变。

That's really the one thing that Scott Meyers got wrong.

If there is an actual algorithm that matches what you need to do, then of course use the algorithm.

But if all you need to do is loop through a collection and do something to each item, just do the normal loop instead of trying to separate code out into a different functor, that just ends up dicing code up into bits without any real gain.

There are some other options like boost::bind or boost::lambda, but those are really complex template metaprogramming things, they do not work very well with debugging and stepping through the code so they should generally be avoided.

As others have mentioned, this will all change when lambda expressions become a first class citizen.

错々过的事 2024-07-13 07:45:17

for 循环是命令式的,算法是声明性的。 当你编写std::max_element时,很明显你需要什么,当你使用循环来实现相同的目的时,则不一定如此。

算法也可能具有轻微的性能优势。 例如,当遍历 std::deque 时,专门的算法可以避免冗余地检查给定增量是否将指针移动到块边界上。

然而,复杂的函子表达式很快就会导致算法调用变得不可读。 如果显式循环更具可读性,请使用它。 如果算法调用可以在没有十层绑定表达式的情况下表达,那么无论如何都更喜欢它。 这里,可读性比性能更重要,因为这种优化正是高德纳 (Knuth) 对霍尔 (Hoare) 的著名评价; 一旦您意识到它是一个瓶颈,您就可以毫无问题地使用另一个构造。

The for loop is imperative, the algorithms are declarative. When you write std::max_element, it’s obvious what you need, when you use a loop to achieve the same, it’s not necessarily so.

Algorithms also can have a slight performance edge. For example, when traversing an std::deque, a specialized algorithm can avoid checking redundantly whether a given increment moves the pointer over a chunk boundary.

However, complicated functor expressions quickly render algorithm invocations unreadable. If an explicit loop is more readable, use it. If an algorithm call can be expressed without ten-storey bind expressions, by all means prefer it. Readability is more important than performance here, because this kind of optimization is what Knuth so famously attributes to Hoare; you’ll be able to use another construct without trouble once you realize it’s a bottleneck.

原来是傀儡 2024-07-13 07:45:17

这取决于,如果算法不采用函子,则始终使用 std 算法版本。 对您来说,写起来更简单,也更清晰。

对于采用仿函数的算法,通常不会,直到可以使用 C++0x lambda。 如果函子很小并且算法很复杂(大多数都不是),那么仍然使用 std 算法可能会更好。

It depends, if the algorithm doesn't take a functor, then always use the std algorithm version. It's both simpler for you to write and clearer.

For algorithms that take functors, generally no, until C++0x lambdas can be used. If the functor is small and the algorithm is complex (most aren't) then it may be better to still use the std algorithm.

甜嗑 2024-07-13 07:45:17

原则上我是 STL 算法的忠实粉丝,但实际上它太麻烦了。 当您定义函子/谓词类时,两行 for 循环可能会变成 40 多行代码,这些代码突然变得更难理解 10 倍。

值得庆幸的是,在 C++0x 中,有了 lambda 函数、auto 和新的 for 语法,事情将会变得大大容易。 在 Wikipedia 上查看C++0x 概述

I'm a big fan of the STL algorithms in principal but in practice it's just way too cumbersome. By the time you define your functor/predicate classes a two line for loop can turn into 40+ lines of code that is suddenly 10x harder to figure out.

Thankfully, things are going to get a ton easier in C++0x with lambda functions, auto and new for syntax. Checkout this C++0x Overview on Wikipedia.

输什么也不输骨气 2024-07-13 07:45:17

我不会为此使用硬性规定。 有很多因素需要考虑,例如您经常在代码中执行特定操作,只是一个循环或“实际”算法,该算法是否依赖于您必须传输到函数的大量上下文?

例如,我不会将类似的东西

for (int i = 0; i < some_vector.size(); i++)
    if (some_vector[i] == NULL) some_other_vector[i]++;

放入算法中,因为这会导致更多的代码百分比,并且我必须以某种方式处理让算法知道的 some_other_vector 。

还有许多其他示例表明使用 STL 算法非常有意义,但您需要根据具体情况做出决定。

I wouldn't use a hard and fast rule for it. There are many factors to consider, like often you perform that certain operation in your code, is just a loop or an "actual" algorithm, does the algorithm depend on a lot of context that you would have to transmit to your function?

For example I wouldn't put something like

for (int i = 0; i < some_vector.size(); i++)
    if (some_vector[i] == NULL) some_other_vector[i]++;

into an algorithm because it would result in a lot more code percentage wise and I would have to deal with getting some_other_vector known to the algorithm somehow.

There are a lot of other examples where using STL algorithms makes a lot of sense, but you need to decide on a case by case basis.

瞎闹 2024-07-13 07:45:17

我认为 STL 算法接口不是最优的,应该避免,因为直接使用 STL 工具包(对于算法)可能在性能上有很小的提升,但肯定会降低可读性、可维护性,甚至在您学习如何使用这些工具时具有一点可写性

向量上的标准 for 循环

int weighted_sum = 0;
for (int i = 0; i < a_vector.size(); ++i) {
  weighted_sum += (i + 1) * a_vector[i];  // Just writing something a little nontrivial.
}

比使用 for_each 构造或尝试将其放入累加调用中效率高多少?

您可能会说迭代过程效率较低,但 for _each 还在每个步骤引入了一个函数调用(通过尝试内联函数可以缓解这种情况,但请记住“内联”是只是对编译器的建议 - 它可能会忽略它)。

无论如何,差异都很小。 根据我的经验,您编写的超过 90% 的代码不是性能关键,但编码时间关键。 通过保持 STL 循环完全内联,它的可读性非常好。 对于你自己或未来的维护者来说,绊倒的间接因素更少。 如果它在你的风格指南中,那么你就可以为你的程序员节省一些学习时间(承认吧,第一次学习正确使用 STL 会遇到一些陷阱)。 最后一点就是我所说的可写性成本的意思。

当然,也有一些特殊情况 - 例如,您实际上可能希望将 for_each 函数分开以便在其他几个地方重复使用。 或者,它可能是少数几个对性能高度关键的部分之一。 但这些都是特殊情况——例外而不是规则。

I think the STL algorithm interface is sub-optimal and should be avoided because using the STL toolkit directly (for algorithms) might give a very small gain in performance, but will definitely cost readability, maintainability, and even a bit of writeability when you're learning how to use the tools.

How much more efficient is a standard for loop over a vector:

int weighted_sum = 0;
for (int i = 0; i < a_vector.size(); ++i) {
  weighted_sum += (i + 1) * a_vector[i];  // Just writing something a little nontrivial.
}

than using a for_each construction, or trying to fit this into a call to accumulate?

You could argue that the iteration process is less efficient, but a for _ each also introduces a function call at each step (which might be mitigated by trying to inline the function, but remember that "inline" is only a suggestion to the compiler - it may ignore it).

In any case, the difference is small. In my experience, over 90% of the code you write is not performance critical, but is coder-time critical. By keeping your STL loop all literally inline, it is very readable. There is less indirection to trip over, for yourself or future maintainers. If it's in your style guide, then you're saving some learning time for your coders (admit it, learning to properly use the STL the first time involves a few gotcha moments). This last bit is what I mean by a cost in writeability.

Of course there are some special cases -- for example, you might actually want that for_each function separated to re-use in several other places. Or, it might be one of those few highly performance-critical sections. But these are special cases -- exceptions rather than the rule.

野鹿林 2024-07-13 07:45:17

IMO,应该避免使用像 std::for_each 这样的许多标准库算法 - 主要是因为其他人提到的缺乏 lambda 问题,但也因为存在不适当的细节隐藏之类的问题。

当然,将细节隐藏在函数和类中是抽象的一部分,一般来说,库抽象比重新发明轮子更好。 但抽象的一项关键技能是知道什么时候该做,什么时候不该做。 过度抽象会损害可读性、可维护性等。良好的判断来自于经验,而不是来自于僵化的规则——当然,在你学会打破规则之前,你必须先学习规则。

OTOH,值得考虑的事实是,许多程序员已经使用 C++(以及在此之前的 C、Pascal 等)很长时间了。 旧习难改,有一种叫做认知失调的东西,它常常会导致借口和合理化。 不过,不要急于下结论——标准制定人员至少有可能犯有决策后不一致的问题。

IMO, a lot of standard library algorithms like std::for_each should be avoided - mainly for the lack-of-lambda issues mentioned by others, but also because there's such a thing as inappropriate hiding of details.

Of course hiding details away in functions and classes is all part of abstraction, and in general a library abstraction is better than reinventing the wheel. But a key skill with abstraction is knowing when to do it - and when not to do it. Excessive abstraction can damage readability, maintainability etc. Good judgement comes with experience, not from inflexible rules - though you must learn the rules before you learn to break them, of course.

OTOH, it's worth considering the fact that a lot of programmers have been using C++ (and before that, C, Pascal etc) for a long time. Old habits die hard, and there is this thing called cognitive dissonance which often leads to excuses and rationalisations. Don't jump to conclusions, though - it's at least as likely that the standards guys are guilty of post-decisional dissonance.

看春风乍起 2024-07-13 07:45:17

我认为一个重要因素是开发商的舒适程度。

使用transform或for_each可能确实是正确的做法,但它并没有更有效,而且手写循环本质上并不危险。 如果开发人员需要花半个小时来编写一个简单的循环,而不是花半天时间来获得正确的transform或for_each语法,并将提供的代码移动到函数或函数对象中。 然后其他开发人员需要知道发生了什么。

对于新开发人员来说,最好的方法可能是学习使用 Transform 和 for_each 而不是手工循环,因为他能够一致地使用它们而不会出现错误。 对于我们其他人来说,编写循环是第二天性,最好坚持我们所知道的,并在业余时间更加熟悉算法。

这么说吧——如果我告诉我的老板我花了一天时间将手工循环转换为 for_each 和转换调用,我怀疑他会很高兴。

I think a big factor is the developer's comfort level.

It's probably true that using transform or for_each is the right thing to do, but it's not any more efficient, and handwritten loops aren't inherently dangerous. If it would take half an hour for a developer to write a simple loop, versus half a day to get the syntax for transform or for_each right, and move the provided code into a function or function object. And then other developers would need to know what was going on.

A new developer would probably be best served by learning to use transform and for_each rather than handmade loops, since he would be able to use them consistently without error. For the rest of us for whom writing loops is second nature, it's probably best to stick with what we know, and get more familiar with the algorithms in our spare time.

Put it this way -- if I told my boss I had spent the day converting handmade loops into for_each and transform calls, I doubt he'd be very pleased.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文