Iterating through containers… options, options options.
Iterating through a container of objects is something we do a lot. It’s boilerplate code. It’s also a nice indicator of how C++ is raising the level of abstraction in programming.
So let’s start with a simple container of ADTs:
Now let’s iterate through them, calling the mf() method on each object in turn. First, we’ll ‘roll our own’ loop.
That’s imperative programming in action; and has the warm, fuzzy feeling of recognition for all C programmers.
Modern C++, however, favours a more declarative style of programming (even though, under the hood, it’s all imperative!) Typically, you’d use an algorithm to declare what you want to happen (in this case, iterate across a range of objects in a container, doing ‘something’ to each one) rather than how:
This is certainly more abstract, but I’m not sure it’s much more readable (am I the only person who feels the member function adapters are named the wrong way round?)
A brief look at the code for for_each reveals that it pretty much does what your hand-rolled code would do.
With the release of C++11 we have new options available to us. First up is the lambda:
The syntax for lambdas tends to provoke upset in more-sensitive souls but once you’re used to it the algorithms become quite elegant: I define what I want to happen exactly at the point I need it. Now I’m specifying what I want to achieve, and how I want to achieve it all in one one lovely confection of declarative and imperative code!
Finally, in a random act of good sense the C++ committee realised that iterating through containers is such a common thing to do it should be added to the language rather than being relegated to a library function – just like in other modern programming languages:
The range-for statement provides a single (compound) statement solution to accessing each element of a container. And it works with all the standard containers – even good ol’ fashioned arrays.
But surely, such an abstract statement will be woefully inefficient; or put another way: “it can’t possibly be as efficient as my hand-rolled loop”. I did a quick comparison:
Hand-rolled loop:
- 35 lines of assembler, including:
- 8 function calls
- std::vector<T>::begin
- std::vector<T>::end
- std::vector<T>::iterator::operator ++
- std::vector<T>::iterator::operator !=
- std::vector<T>::iterator::operator –>
- std::vector<T>::iterator::~iterator (called in two places)
- The member function call
Range-for statement:
- 34 lines of assembler, including:
- 8 function calls:
- std::vector<T>::begin()
- std::vector<T>::end()
- std::vector<T>::iterator::operator ++
- std::vector<T>::iterator::operator !=
- std::vector<T>::iterator::operator *
- std::vector<T>::iterator::~iterator (called in two places)
- The member function call.
So, to all practical intents and purposes the code is the same. So, with C++11 there really is no compelling reason for rolling your own iteration loops.
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
These statements are hilariously genius and help clarify the unconscious cognitive dissonance that keep people back from going to the next level:
- "The syntax for lambdas tends to provoke upset in more-sensitive souls but once you’re used to it the algorithms become quite elegant:"
- "That’s imperative programming in action; and has the warm, fuzzy feeling of recognition for all C programmers."