Discovering C++11

I've never claimed to be the most proficient coder, and I'll happily admit I still have much to learn. One of the things I've been struggling with in recent months is the constant evolution of languages - it often seems to me that by the time I've learned something well enough to use it in a reasonably professional way it has already been deprecated. This is true for more or less all languages, although languages aimed at enterprise development such as C# or Java seem to suffer more (with the exception of GLSL which seems to change almost every month - don't get me started on that...), including C++ which, in its latest iteration, has been ratified as C++11. Having personally come from a background of writing embedded software in C the last few years spent learning C++ have involved identifying the main differences between the commonly held practices of each language (they are, after all, still independent languages) and learning to choose the correct one for the job. This has meant that, in the main, when using C++ I have been studying C++98/03. I have learned a lot from (and am very grateful to) many sources on the web and it has been quite hard work, which makes it very difficult for me to want to move on from some of those things which have been deprecated or improved upon by C++11 as, in some ways, it feels as though I'm reinventing the wheel. This is actually untrue, of course, and recently two features of C++11 have been brought to my attention which particularly prove me wrong in refusing to move on, so I thought I would write about them in case anyone else reading this is almost as stubborn as I am.

Type Inference
    In C and C++ (up to C++11) all data types must be specifically stated - which makes sense really, I have no problem with that. When using the STL with templated objects, however, I can see how having the actual data type inferred by the compiler can be useful. I noticed this particularly when using STL containers / iterators for example. Imagine you were using a std::vector of MyObj pointers and you wanted to loop through it. This can be done in a couple of ways (all ways I have used in the past, I may add, while evolving my own personal understanding of the language):

The 'C' style way (STL containers don't actually exist in C of course):

for(unsigned i = 0; i < myVec.size(); i++)
{
   myVec[i]->doStuff();
}

or the more 'correct' C++ way using iterators:

for(std::vector<MyObj*>::const_iterator it = myVec.begin();
 it != myVec.end(); ++it)
{
    MyObj& obj = **it;
    obj.doStuff();
}

Both work fine and are completely acceptable although the second version may be tuned to work a little more efficiently by the STL. When declaring the iterator in the second version, however, it could be  considered a bit... wordy, and can be shortened using the C++11 definition of the keyword

auto

In earlier versions of C++ and in C auto is a storage class specifier along with static, extern and register. While these other specifiers have very er... specific uses, auto has come to be pretty much needless as all data types are assumed auto by default when none of the other specifiers are used. The good folks who maintain the C++ specification have, therefore, come up with a new use for it as a keyword for type inference. Taking the vector example above you could rewrite it like so:

for(auto it = myVec.cbegin(); it != myVec.cend(); ++it)
{
    //stuff
}

Not only is it a much shorter way of writing out the loop it has the added bonus of making updates to the template's object much easier. Consider a class which has a member std::deque<MyObj*> myDeque. If several of the functions in the class each loop over myDeque more than once and then you decide to update it from std::deque<MyObj*> to std::deque<MyOtherObj*> you'll find yourself with multiple instances of std::deque<MyObj*>::const_iterator to update as well. Ok, so it's not usually a problem with the find and replace function of most modern IDEs but it is all extra work. Use of the auto keyword mitigates this entirely as the iterator type is automatically inferred. In fact you could even change the deque to one of the other STL containers such as a vector and it would still work. Needless to say I shall be using auto where I can from now on. I've tested it to work with the VC compiler version 10 and GCC from version 4.7 (and it probably works with earlier versions) with the C++0x or C++11 compiler flags.

The second new feature of C++11 I've found useful is also linked to the for loop example. In other languages such as C# you have the ability to loop through lists, arrays and containers using the foreach keyword. C++11 now offers similar functionality albeit with a slightly different syntax.

Range-based for-loop
    This works for any STL container which provides begin() and end() functions that return iterators, which can be used to further shorten the example above, as well as working with traditional C style arrays. Taking the latter we have this example:

int array[5] = {1, 2, 3, 4, 5};
for(int i = 0; i < 5; i++)
{
    array[i] += 5;

}

which can now be written as

int array[5] = {1, 2, 3, 4, 5};
for(int &i : array)
{
    i += 5;

}

Not only does this make writing the for loop shorter it makes resizing array easier at a later date. Should you wish to update int array[5] to int array[10] you no longer have to hunt down every loop and update it with the new size, similarly to the type inference example.

So to conclude: the evolution of languages is a good thing (usually): new features are often useful and worthwhile learning, and I must be less wary of incoming features because, ultimately, they are there to help.

Comments

  1. For me the most important new features with C++11 are the smart pointers. They make the language itself much safer and replace the cumbersome use of raw pointers with new/delete.

    Btw. you're 'auto' example is a bit flawed. Above you used a const_iterator, but if you now go and just use auto it = vec.begin() you'll get a non-const iterator. That's why there are now the functions cbegin() and cend().

    Also for the range-base for loop the auto keyword can also be quite useful, so you don't have to really care what the type of the container's content you're iterating over actually is and let the loop body take care of that.

    ReplyDelete
    Replies
    1. Oops! I've updated the post now. I shall definitely be looking into smart pointers next, thanks for the comment :)

      Delete

Post a Comment

Popular Posts