The changing face of programming abstraction in C++

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:

image

Now let’s iterate through them, calling the mf() method on each object in turn.  First, we’ll ‘roll our own’ loop.

image

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:

image

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:

image

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:

image

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:

image

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.

Posted on July 27th, 2012
» Feed to this thread
» Trackback

Leave a Reply