Sunday 6 November 2011

Getting started with C++11

Wow, has it really been this long since my last post?  It's been a mad few months getting the bathroom and kitchen finished off, dealing with a young child and now baby number 2 only a couple of weeks away.  With all of this going off I've somewhat neglected this blog and it's about time I started putting some articles up.

So I thought I'd try to get back into the swing of things by writing a few brief entries on getting going with some of the new features of the recently C++11 standard.  I've been keeping an eye on the process over the last year and I'm excited by the new features available to us being introduced in newer versions of the compilers.  All of the code I'll be putting up has been written and compiled on a laptop running Ubuntu 11.10 (Oneiric Ocelot) which has GCC 4.6.1 in the repositories (this has a list of the C++11 features available at the various releases), most things are coming along nicely but concurrency is still a way off.

Auto's

Quite possibly the most useful new feature in day-to-day use is the new "auto" keyword - if you know C# then this can be compared to the "var" keyword (which is not the same as the VB variant type) - which can be used in place of specifying a variable type where the type can be inferred by the compiler at the point of declaration.  So instead of typing:

int x = 42;

You can instead use:

auto x = 42;

This means that the compiler will infer x as an integer, after this point x will always be an integer (in the current scope).  This is most likely not something you will do day-to-day (personally I won't be likely to) but then this isn't where it's use shines through, lets instead look at another example:

std::vector<std::string> my_collection;
my_collection.push_back("Hello");
my_collection.push_back("World");

for (std::vector<std::string>::iterator it = my_collection.begin(); it != my_collection.end(); ++it)
{
    cout << *it << endl;
}

So, a simple collection that we then iterate over and write the value out to the console.  So where can the "auto" keyword help here?  Well that for loop is looking pretty doesn't it?  Wouldn't it be nice if there was some way we could tidy it up a little, maybe get it looking a little more like this:

for (auto it = my_collection.begin(); it != my_collection.end(); ++it)
{
    cout << *it << endl;
}

Full example code

And guess what, we can (yay!).  This is because when we declare "it" the compiler can infer it's type so we don't have to clutter up our code specifying the type when we already know what it is.  There is actually a few more things we can do to this example to make it even easier to read with new features but they'll come later.

As a Return Type

Yep, we can use the "auto" keyword in place of a return type as well, how does this work though as we're not specifying a variable, so how do we infer type?  Well we can now specify the return type at the end of the function declaration, so instead of: 

int Sum(int x, int y) { ... }

We can instead use the "auto" keyword and specify the type at the end:

auto Sum(int x, int y) -> int { ... }

Which doesn't look much better does it?  Well again this isn't really the intended use of the syntax, but if this isn't then what is? Well one place is where the type being returned is not known to the compiler at the point of definition.  Consider the following snippet from a header file:

class Test
{
public:
    enum TestEnum { One, Two, Three };
    void SetField(TestEnum t);
    TestEnum GetField();
private:
    TestEnum _field;
};

Implementing the setter is easy in the source file, we just write the following:

void Test::SetField(TestEnum t) { ... }

And for the getter we just write this:

TestEnum Test::GetField() { return _field; }

Dont we?  Well, no actually.  The compiler will return an error as the return type TestEnum is not known to the compiler at the point where we define the return type, to get this to work we would need to do the following:

Test::TestEnum Test::GetField() { return _field; }

Alternatively, using the "autokeyword as the return type and using the new return type syntax we could type the following instead:

auto Test::GetField() -> TestEnum { return _field; }

Full example code

This works because the compiler knows about TestEnum at the point where we now define the return type.  Still this doesn't look like it provides much benefit, but it will when we introduce the final new piece of syntax for this post.

decltype

This is an operator which is used to determine the type of an expression or variable so you can create a variable based on that type, like this:

int x = 3;
decltype(x) y = 5;       // same as int y = 5
decltype(x - y) z = 7;   // same as int z = 7

So far so good but again it doesn't look like it's bringing much to the party.  So what if we do the following instead:

std::map<int, std::vector<std::string>> MyFunction() { ... }
auto MakeCollection() -> decltype( MyFunction() )
{
    auto val = MyFunction();
    return val;
}

Full code example

Take a second, read it again, now think of all those poor keys on your keyboard, don't they deserve a break?  At this point the use of decltype and the new return type syntax and the new auto keyword all should hopefully look really useful and the kind of things you might want to start using a bit more frequently, they did for me when I first figured it out.  The whole thing looks even more appealing when you start considering templated functions as well when sometimes the return type can be more difficult if not impossible to figure out.  Also you are reading that right, I did write ">>" in there, the new specification treats this the way we read it which makes a lot more sense thankfully.

Just for completeness, here's the above snippet of code written using the more traditional syntax:

std::map<int, std::vector<std::string> > MyFunction() { ... }
std::map<int, std::vector<std::string> > MakeCollection()
{
    std::map<int, std::vector<std::string> > val = MyFunction();
    return val;
}

Anyone who says that last snippet is easier to read is either lying or wants their head examining, it's bad enough typing it!  So go on and give these new features a try, if you're not wanting to use them most of the time after a week I'll be shocked.

I'm planning my next post of this type to be about initializer lists and for-range loops, after which I will hopefully look at lamda expressions and smart pointers.  The items discussed above and the ones coming up - I feel - are the first things which makes C++ based on the new C++11 standard feel like a modern programming language and, hopefully, keeps new and experiences programmers coming back to it for years to come.

References
Wikipedia - C++11
CProgramming.com - C++11 articles
Bjarne Stroustrup - C++11 FAQ

All code provided in this article is provided under a BSD license.  If you spot an error then please do let me know so that we can make this better for anyone else reading it.
Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

No comments:

Post a Comment