views:

356

answers:

9

Disclaimer: I tried to search for similar question, however this returned about every C++ question... Also I would be grateful to anyone that could suggest a better title.

There are two eminent loop structure in C++: while and for.

  • I purposefully ignore the do ... while construct, it is kind of unparalleled
  • I know of std::for_each and BOOST_FOREACH, but not every loop is a for each

Now, I may be a bit tight, but it always itches me to correct code like this:

int i = 0;
while ( i < 5)
{
  // do stuff
  ++i; // I'm kind and use prefix notation... though I usually witness postfix
}

And transform it in:

for (int i = 0; i < 5; ++i)
{
  // do stuff
}

The advantages of for in this example are multiple, in my opinion:

  1. Locality: the variable i only lives in the scope of the loop
  2. Pack: the loop 'control' is packed, so with only looking at the loop declaration I can figure if it is correctly formed (and will terminate...), assuming of course that the loop variable is not further modified within the body
  3. It may be inlined, though I would not always advised it (that makes for tricky bugs)

I have a tendency therefore not to use while, except perhaps for the while(true) idiom but that's not something I have used in a while (pun intended). Even for complicated conditions I tend to stick to a for construct, though on multiple lines:

// I am also a fan of typedefs
for (const_iterator it = myVector.begin(), end = myVector.end();
     it != end && isValid(*it);
     ++it)
{ /* do stuff */ }

You could do this with a while, of course, but then (1) and (2) would not be verified.

I would like to avoid 'subjective' remarks (of the kind "I like for/while better") and I am definitely interested to references to existing coding guidelines / coding standards.

EDIT:

I tend to really stick to (1) and (2) as far as possible, (1) because locality is recommended >> C++ Coding Standards: Item 18, and (2) because it makes maintenance easier if I don't have to scan a whole body loop to look for possible alterations of the control variable (which I takes for granted using a for when the 3rd expression references the loop variables).

However, as gf showed below, while do have its use:

while (obj.advance()) {}

Note that this is not a rant against while but rather an attempt to find which one of while or for use depending on the case at hand (and for sound reasons, not merely liking).

+2  A: 

i does not automatically increase within a while loop.

while (i < 5) {

   // do something with X

   if (X) {
       i++;
   }

}
Martin Hohenberg
Exact, I forgot to cite a case where while is useful.
Matthieu M.
I would advocate this point as a case against while. It has happened before that I forget to increase a variable in thew while somewhere... something which in a for loop you're less likely to do
Toad
Actually, it is useful:while (successfullLaunches < 5) { if (Launch()) { i++; }}
Martin Hohenberg
+2  A: 

One of the most beautiful stuff in C++ is the algorithms part of STL. When reading code written properly using STL, the programmer would be reading high-level loops instead of low-level loops.

AraK
I appreciate the sentiment, but as I said, not everything is a `for each`. Also, as long as we don't have lambdas, the use of stl algorithms sometimes really feel awkward, and it may hurt readability more than helping it. Note that BOOST_FOREACH is really great in this regard.
Matthieu M.
Yep... I really agree. We need lambdas a lot :)
AraK
+7  A: 

Not all loops are for iteration:

while(condition) // read e.g.: while condition holds
{
}

is ok, while this feels forced:

for(;condition;)
{
}

You often see this for any input sources.

You might also have implicit iteration:

while(obj.advance())
{
}

Again, it looks forced with for.

Additionally, when forcing for instead of while, people tend to misuse it:

for(A a(0); foo.valid(); b+=x); // a and b don't relate to loop-control
Georg Fritzsche
Ah, indeed, a cursor example is perfect to illustrate the use of while!
Matthieu M.
There are so many misuses possible in c++, that I would not hold it against any construct / guideline.
Matthieu M.
"Not all loops are for iteration" - yes, they are! That's the very definition of iteration. Iterate means Repeat. I think what you mean is that not all loops have a controlling incrementing or decrementing variable.
paxdiablo
A: 

I tend to use for loops when there is some kind of counting going on and the loop ends when the counting ends. Obviously you have your standard for( i=0; i < maxvalue; i++ ), but also things like for( iterator.first(); !iterator.is_done(); iterator.next() ).

I use while loops when it's not clear how many times the loop might iterate, i.e. "loop until some condition that cannot be pre-computed holds (or fails to hold)".

Graeme Perrow
+3  A: 

Functionally, they're the same thing, of course. The only reason to differentiate is to impart some meaning to a maintainer or to some human reader/reviewer of the code.

I think the while idiom is useful for communicating to the reader of the code that a non-linear test is controlling the loop, whereas a for idiom generally implies some kind of sequence. My brain also kind of "expects" that for loops are controlled only by the counting expression section of the for statement arguments, and I'm surprised (and disappointed) when I find someone conditionally messing with the index variable inside the execution block.

You could put it in your coding standard that "for" loops should be used only when the full for loop construct is followed: the index must be initialized in the initializer section, the index must be tested in the loop-test section, and the value of the index must only be altered in the counting expression section. Any code that wants to alter the index in the executing block should use a while construct instead. The rationale would be "you can trust a for loop to execute using only the conditions you can see without having to hunt for hidden statements that alter the index, but you can't assume anything is true in a while loop."

I'm sure there are people who would argue and find plenty of counter examples to demonstrate valid uses of for statements that don't fit my model above. That's fine, but consider that your code can be "surprising" to a maintainer who may not have your insight or brilliance. And surprises are best avoided.

John Deters
i agree. communication is everything. so when two code have very closed efficiency, please choose readable one, and don't give other people any big surprise, unless you'll be the only maintainer, or the code is time-consuming sensitive extremely and explicit notice will be provided.
EffoStaff Effo
They're not entirely the same... if you have an iterator increment at the end of a `while` loop, then `continue` will skip it. Whereas with a `for` loop, `continue` will still execute the increment. This difference results in more than a few infinite loops for new developers.
Tom
Tom, that's why a for loop should follow certain guidelines. When you write "new developers" you really mean "Surprise! Gotcha!" and that's why they should be steered down the path of least surprise. A coding standard that explained when to use each would help a new developer understand that difference.
John Deters
I'm not sure if it's great practice, but I occasionally find myself using a for(int i = 0; (i < 10) and (!stopEarly); i++){...}where someting inside the loop can cause execution to stop - for example searchinglinearly through an array to check existence if there's on coll.contains() type of function.
Knobloch
@EffoStaffEffo: being the only maintainer means personal project, usually you can't assume no-one else will ever have to look at it.
Matthieu M.
@Tom: I also use 'continue' or 'break' within for loops, it doesn't matter since I the `for` construct guarantees (usually) the termination.
Matthieu M.
@jadeters: That's the whole point of the question, having an objective way to decide rather than relying on the mood of the moment or personal preference.
Matthieu M.
That's why I suggested a hard-and-fast rule. C and C++ are languages that don't follow the convention of others that enforce an inviolate index in a for statement. My suggestion is that you permit only well-defined for statements and relegate the "messy" looping constructs to while statements. That's an objective measure, one you can easily enforce in a standard or even a static code analysis tool.
John Deters
+1  A: 

I don't believe that compilers can optimize significantly better if you chose to express your loop one way or the other. In the end it all boils down to readability, and that's a somewhat subjective matter (even though most people probably agree on most examples' readability factor).

As you have noticed, a for loop is just a more compact way of saying

type counter = 0;
while ( counter != end_value )
{
  // do stuff
  ++counter;
}

While its syntax is flexible enough to allow you to do other things with it, I try to restrict my usage of for to examples that aren't much more complicated than the above. OTOH, I wouldn't use a while loop for the above.

sbi
A: 
// I am also a fan of typedefs
for (const_iterator it = myVector.begin(), end = myVector.end();
     it != end && isValid(*it);
     ++it)
{ /* do stuff */ }

It seems to me that the above code, is rather less readable than the code below.

// I am also a fan of typedefs
const_iterator it = myVector.begin();
end = myVector.end();
while(it != end && isValid(*it))
{
    /* do stuff */ 
    ++it}

Personally, I think legibility trumps these kind of formatting standards. If another programmer can't easily read your code, that leads to mistakes at worst, and at best it results in wasted time which costs the company money.

Michael Dillon
Ah then it's interesting. I find your example actually less maintainable because one has to get to look at the loop body to decide whether or not the loop control is actually correct.
Matthieu M.
I think that it is dangerous to look at a program in such an abstract way. If you don't understand the loop body, then you cannot possibly know if the loop control is correct. Maybe the increment is in an IF statement. Or maybe there is no increment but it is a side effect of something else in the loop body. You have to understand the whole algorithm that the code is expressing.
Michael Dillon
Yes, you have to understand the whole body to understand the program, however this does not prevents you from focusing on certain points during code reviews: loop termination, undefined behavior and so on > ie the kind of bugs that can really get your manager angry :)
Matthieu M.
A: 

In Ye Olde C, for and while loops were not the same.

The difference was that in for loops, the compiler was free to assign a variable to a CPU register and reclaim the register after the loop. Thus, code like this had non-defined behaviour:

int i;
for (i = 0; i < N; i++) {
    if (f(i)) break;
}

printf("%d", i);           /* Non-defined behaviour, unexpected results ! */

I'm not 100% sure, but I believe this is described in K&R

This is fine:

int i = 0;
while (i < N) {
    if (f(i)) break;
    i++;
}
printf("%d", i);

Of course, this is compiler-dependent. Also, with time, compilers stopped making use of that freedom, so if you run the first code in a modern C compiler, you should get the expected results.

Leonel
I can't say for sure that this is definitely wrong, but I don't think so. I know I've written code that checked the value of i after the loop to see if it went through the entire thing or skipped out in the middle. If `i` is undefined after the loop, this would never have worked.
Graeme Perrow
@Graeme, "undefined" doesn't mean the value is undefined (an int always has a value.) It means "the compiler's behavior is undefined by the standard, so a compiler writer can do anything he wants." One compiler could leave the loop value at its last value when it was in the loop, but a different compiler could leave the loop value at its last value plus another increment. And a new version of your old compiler could set it to zero or even to 0xDEADBEEF! The code may work in your case, but it is not guaranteed to be portable. Code that relies on undefined behavior is "incorrect."
John Deters
A: 

I wouldn't be so quick to throw away do-while loops. They are useful if you know your loop body will run at least once. Consider some code which creates one thread per CPU core. With a for loop it might appear:

for (int i = 0; i < number_of_cores; ++i)
    start_thread(i);

Uentering the loop, the first thing that is checked is the condition, in case number_of_cores is 0, in which case the loop body should never run. Hang on, though - this is a totally redundant check! The user will always have at least one core, otherwise how is the current code running? The compiler can't eliminate the first redundant comparison, as far as it knows, number_of_cores could be 0. But the programmer knows better. So, eliminating the first comparison:

int i = 0;
do {
    start_thread(i++);
} while (i < number_of_cores);

Now every time this loop is hit there is only one comparison instead of two on a one-core machine (with a for loop the condition is true for i = 0, false for i = 1, whereas the do while is false all the time). The first comparison is omitted, so it's faster. With less potential branching, there is less potential for branch mispredicts, so it is faster. Because the while condition is now more predictable (always false on 1-core), the branch predictor can do a better job, which is faster.

Minor nitpick really, but not something to be thrown away - if you know the body will always run at least once, it's premature pessimization to use a for loop.

AshleysBrain
The for loop code is easier to read - it's obvious at the very top of the loop what the loop is testing. Making the code more difficult to read just to save one comparison is a bad idea IMHO.
Graeme Perrow
Well, the point is for if you need hardcore optimisation rather than readability. I guess you're right that most of the time, a for loop is perfectly sufficient.
AshleysBrain
I don't consider the do/while construct in this question, because you can't exactly mimick its behavior with a for in a straightforward fashion (though it's feasible). I really think they address 2 different problems and are not interchangeable.
Matthieu M.