views:

132

answers:

6

I feel like I'm unprofessional in the way I name and use iterators. What I mean by that is that I "feel" like I should be calling them something else, but I always name them based on the "it_" prefix, and after a while, in a long function, the names start to all look alike.

Additionally, I always wonder if I'm doing things a "strange" way that I learned just because I didn't know better. For instance, if I were iterating through a map to display all its key/value pairs, I would do this:

map<int, int>::const_iterator it = layout.begin();
for (; it != layout.end(); ++it)
{
   cout << it->first << ":\t" << it->second << "\n";
}

I see some people calling their iterators "iter" - I see other ways of doing loops. Is there any kind of convention that transcends style and is just good practice?

A: 

The name is the least important thing to me as long as it's clear and chosen appropriately.

This falls into the endless debate of variable naming

  • m_ for members
  • trailing underscore for members
  • etc...
David
+3  A: 

I try to declare the iterators inside the for loop as much as possible, so that the scope of the iterator identifier is minimized.

First, I typedef the "long" type names to "shorter" and re-usable ones:

typedef std::map< int, int > IntMap;
typedef IntMap::const_iterator IntMapConstIter;

Then, I do

for( IntMapConstIter it = layout.begin(); it != layout.end(); ++it ) {
    ....
}
ArunSaha
I hadn't even considered that - and one of the things I was worried about *was* scope. How do you deal with the incredibly long "for" statements that tend to result? The best I can think of is making a typedef for the datatype to shorten it.
Tim
Just wrap it, see my answer. I find it more readable than declaring a variable outside of the loop.
Adam Byrtek
i personally typedef the datatype, then separate the for statement into its 3 pieces on 3 different lines.
Benoît
This is extremely readable, and I also hadn't ever considered using a typedef for the const_iterator. Makes a *lot* of sense, and its easy to deal with.
Tim
I don't like to use typedefs just for the sake of abbreviation, it increases complexity of otherwise simple code for not much benefit.
Adam Byrtek
One of the best parts of C++0x is `auto it = layout.begin();`.
Brendan Long
@Brendan: Good point. However, I don't (yet) understand how `iterator` versus `const_iterator` is resolved. [+1]
ArunSaha
@Adam: my thought too. Hence, it's only for reduced redundancy, ease of changing container type, and readability that I use typedefs like IntMap above, but I'd then use IntMap::const_iterator as I don't see IntMapConstIter being remotely more understandable, it's only minimally smaller, loses information re the relationship to IntMap, and abstracts nothing as it can't be changed to any other type of iterator later without needing to be renamed and have client usage reexamined.
Tony
@ArunSaha: 1 answer: `auto` distinguish between `iterator` and `const_iterator` by type inference, if the container is `const` it'll be a `const_iterator` otherwise an `iterator. 2 remarks: the second *typedef* obscures the code more than it helps it, the first *typedef* should have a **functional** name, the exact type is an implementation detail that may evolve.
Matthieu M.
@Matthieu M: +1 Thanks for the answer. On your remark: (a) I incline to include the container type in the typedef, more so after reading "Beware the illusion of container-independent code" in Effective STL, Scott Meyers [http://www.aristeia.com/estl/index.html]. (b) If there is a map in a class, there freqeuntly is more than one method where I need the iterator. So, typedef'ing it once saves me typing :) I follow a naming convention which helps me immediately recognize what a type means.
ArunSaha
+2  A: 

I do that as follows

for (map<int, int>::const_iterator it = layout.begin();
     it != layout.end(); ++it)
{
    cout << it->first << ":\t" << it->second << "\n";
}

Thanks to that the iterator is scoped only inside the block, which makes it safer to give it a shorter name.

Adam Byrtek
Great! I saw ArunSaha give the same answer before, and this entire time I hadn't even thought of breaking the "for" loop into several lines to deal with the length. Thanks!
Tim
You're welcome, sometimes the simplest solution is the hardest one to notice.
Adam Byrtek
+2  A: 

I am pretty sure no convention transcends style for this matter. I believe the most important part of iterator naming is on the second part (considering you decide to add a prefix, should it be "it" or "iter_" or anything else). Choose the second part carefully as you would for any other variable and you'll be fine.

Benoît
+2  A: 

Would it be totally unreasonable for me to suggest the use of BOOST_FOREACH and for you to (mostly) forget about iterators ?

Benoît
Not at all, I just haven't used Boost yet - even though I know in general all the cool things it offers
Tim
@Benoît: I've not used boost's foreach (have used project-specific implementations). Still, thinking at the iterator level is good when you're inserting or deleting during iteration, tracking multiple iterators concurrently etc.. I don't think boost foreach offers anything for those circumstances...?
Tony
@Tony: it doesn't but modifying the underlying structure of the container while iterating over it... it tricky to do right. I much prefer to either use `algorithm` for the task OR use an alternate temporary data structure that I'll switch with the current one at the end of the loop.
Matthieu M.
@Matthieu: tricky - yes; good alternatives where viable. Cheers.
Tony
+1  A: 

I haven't seen a single canonical loop.

The cost of creating an iterator is specified to be O(1) (wrt the size of the container), but it may cost, especially when specific debugging options are activated.

Therefore, calling end at each iteration of the loop is wasteful.

The canonical way to write a for loop is therefore to compute both it and end in the first statement.

typedef std::map<int,int> LayoutType;

for (LayoutType::const_iterator it = layout.begin(), end = layout.end();
     it != end; ++it)
{
  // ...
}

I usually typedef my container, but honestly there isn't much point in typedefing the iterator, it's already available within the container and introducing too many synonyms does not help.

Also, it may be a personal quirk, but I prefer stating the function rather than the type when I introduce my typedef. The type may change in later refactoring, the function will not (at least, it won't be the same thing at all if it does, so it'll call for a complete rewrite).

For example what if you suddenly preferred: typedef std::unordered_map<int,int> LayoutType ? It still matches your need, and you can probably drop it in at no rewriting cost (providing you have used a typedef).

Matthieu M.
+1 for a lot of good points, though I think the whole end precomputation issue is way overblown - only extreme circumstances or bad design result in a slow end() ;-). Further, some ugly operations may change end() during the iteration: nasty enough by itself, but worse still with a cached end() value. All up, not a big deal either way I'd say.
Tony
@Tony: I never said it was the only way, it is however the canonical one I think. Though this loop is advantageously replaced by `BOOST_FOREACH` or the new *range for* expression of C++0x which neatly hide the iterator things.
Matthieu M.
@Matthieu: Yes, was just trying to note my disagreement over that "canonical" claim: if anything I'd say `for (Iterator i = x.find/begin/?(); i != x.end(); ++i)` was canonical, for better or worse (after all, canonical doesn't mean best practice). It's a relief that auto, ranges, foreach etc. are on their way.
Tony
@Tony: definitely a relief :)
Matthieu M.