Ruminations on C++11
Ruminations on C++11
Posted by zirconMatt on Friday, November 4, 2011
Filed under Articles · Tagged with C++, Development
Filed under Articles · Tagged with C++, Development
///////////////////////////////////////////////////////////////////////////
// Preface
//////////
//
// This is a blog about the new version of C++, C++11. It has been many years in the
// making and adds lots of new features to the C++ language. Some of those features
// are already supported by commonly used compilers, a description of these features
// follows.
//
// As you can see, this 'blog' is a bit unusual. It's actually a C++ source code file
// annotated with lots of comments. So it can be read on its own, with the code being
// examples of the things being described, or it can be viewed in Visual Studio (VS)
// 2010, built and even executed. With a few minor adjustments pointed out along the
// way, it can also be built using GCC and executed on whatever system that it is
// installed on.
/////////////////////////////////////////////////////////////////////////////////////////
// Ignore these, I couldn't think of anywhere else to put them.
#include "stdafx.h"
#include <vector>
#include <algorithm>
#include <map>
#include <iostream>
/////////////////////////////////////////////////////////////////////////////////////////
// Ruminations on C++11 (the bits currently supported anyway).
//////////////////////////////////////////////////////////////
//
// Of the many new features of C++11, only a subset are available on stable releases of
// the well-used contemporary C++ compilers. I thought that it would be a good exercise
// in understanding these new features to try them out. I decided that I'd try VS2010 on
// Windows 7 and GCC on Ubuntu, as these are easy for me to access and a reasonable
// approximation of other developers environments.
//
// The main differences being there is no support for nullptr in GCC, and no support for
// initialiser lists in VS2010. Of these, the initialiser lists seem the most useful feature, I hope it gets into VS soon.
//
// The features described here are:
// * static_assert
// * trailing return types
// * constructors calling other constructors in the same class
// * lambda functions
// * initialiser lists
// * a fix for the 'std::map< int, std::vector<int>>' '>>' problem
// * 'auto' variable declarations
// * 'decltype' variable declarations
// * rvalue references, move constructors and perfect forwarding
// * nullptr, improved typing for the null pointer value
//
// I have annotated some C++11 code that shows the new features, this code does not fully
// comply with our coding standards in order for the descriptions to be clearer. This
// blog contains code that compiles with VS2010, the GCC supported C++11 features are
// provided in the comments.
// 1. static_assert
///////////////////
//
// First we have an example of 'static_assert', these are compile time assertions. In
// this case, a check is made to ensure that the size of the type used to instantiate the
// object is sufficient. This check occurs at compile time, so the supplied error message
// is displayed as a compile error if the condition is false.
//
template<class T>
class TemplateTest
{
// if the size of the type used to instantiate an object is too small, a compile
// time error is produced.
static_assert( sizeof(T) > 1, "sizeof(T) not > 1!" );
T m_test;
};
// 2. Trailing return type declarations
///////////////////////////////////////
//
// Next up, an example of trailing return types on method declarations.
// If we have a class that defines an enumeration (very handy for code clarity).
class MyEnumClass
{
public:
enum MyHelpfulEnum
{
An,
Enumeration,
That,
Usually,
Helps,
To,
Clarify,
Code
};
MyEnumClass();
MyEnumClass( MyHelpfulEnum initialValue );
// We can declare the method like this, with a trailing return type,
static auto KeepCodeClear() -> MyHelpfulEnum;
// or like this, it makes no difference to the compiler.
//static MyHelpfulEnum KeepCodeClear();
// But I think it is helpful to keep things consistent.
};
// The advantage of this feature comes when declaring the method implementation, as this
// method returns an enumeration defined within the class. Previously you'd have to
// declare the method decorated with lots of scope to help the compiler know what was
// going on (it has enough to contend with without having to deal with this kind of
// thing). Like so,
//
//MyEnumClass::MyHelpfulEnum MyEnumClass::KeepCodeClear()
//
// But now you can declare the method like this.
auto MyEnumClass::KeepCodeClear() -> MyHelpfulEnum
// The '->' is a little different, but less repetition is involved (sort of).
{
// we'll see more from 'auto' later (why keep a keyword for one use when it can
// have three!?)
return Usually;
}
// 3. One constructor calling another
/////////////////////////////////////
//
// Here we have an example of one constructor calling another, I found it annoying that
// this was missing from C++ in the past.
MyEnumClass::MyEnumClass()
{
// this constructor can call another constructor.
MyEnumClass( Helps );
}
MyEnumClass::MyEnumClass( MyHelpfulEnum initialValue )
{
}
// Here are some function prototypes for the nullptr description (something to look
// forward to).
void NullPointerCall( int notAPointerParameter );
void NullPointerCall( int* aPointerParameter );
// 4. Lambda functions
//////////////////////
//
int _tmain(int argc, _TCHAR* argv[])
{
// Next we have an example of a lambda function (familiar to C# developers). I
// have to admit I'm not a fan of these; if it's a function make it one, at least
// it can be called from other places then. Now I've got that rant out of the way
// we can continue!
//
// If we have a list of interest:
std::vector<int> anInterestingList;
// And that list has some elements, in GCC we can use initialiser lists, like
// so...
//std::vector<int> anInterestingList{1,2,3,4}; // so much nicer.
//
// But for VS we cannot, so add some elements the old fashioned way.
anInterestingList.push_back( 1 );
anInterestingList.push_back( 2 ); // boo
anInterestingList.push_back( 3 );
anInterestingList.push_back( 4 ); // hiss
// Declare an accumulator for our demonstration.
int accumulator( 0 );
// From time to time, it is necessary to loop through all the elements in a list
// (not all that often, but it does happen).
std::for_each( anInterestingList.begin(), anInterestingList.end(),
// Below is the lambda function, the start is defined by '[', the ampersand
// defines the capture list (the variables accessible by the lambda function),
// if the capture list is empty, i.e. there is only '&', all variables in scope
// are available. It seems like good practice to explicitly state what is
// accessible rather than implying everything. If no '&' appears, i.e '[]' is
// used, no variables are captured. There is also, like any other function a
// parameter list.
[&accumulator](int element){ accumulator += element; } );
// the lambda function, accumulates the values in the list.
std::cout << "Accumulator = " << accumulator << std::endl;
// 5. '>>' in variable declarations
///////////////////////////////////
//
// The next feature is more of a fix than anything else. Allowing '>>' in
// declarations. Of course you could always get around this problem by sub
// classing, (or even using typedef, but that's not very OO is it?) but if you did
// do this the error message was, well, ...confusing (I'll leave it at that before
// I say something I shouldn't).
std::map< int, std::vector<int>> lookItWorks;
// It's better to subclass if you can though. This is not a C++11 feature, but I
// rarely see it in C++ and it seems so obviously OO.
class AListOfInts : public std::vector<int>{};
class AMapOfIntvsInts : public std::map< int, AListOfInts>{};
// Of course if you wish to use the constructors of the base classes this method
// is annoying. If only there was a way...
AMapOfIntvsInts anAsideToProceedings;
// Well C++11 allows the use of 'using' to use base class constructors in derived
// classes, which helps this 'problem'. If they were implemented by VS or GCC
// I'd add an example.
// 6. 'auto' types for variable declarations
////////////////////////////////////////////
//
// This very simple extract of code shows another new use of the auto keyword.
// Here you let the compiler automatically deduce the type.
auto autotest( anInterestingList.begin() );
// 7. 'decltype' types for variable declarations
////////////////////////////////////////////////
//
// Similar to the 'auto' type deduction feature above, this time declaring a type
// that is the same as another type.
decltype(autotest) decltypetest(autotest);
// declare decltypetest as the same type as autotest.
std::cout << "*autotest = " << *autotest << std::endl;
std::cout << "*decltypetest = " << *decltypetest << std::endl;
// 8. rvalue references
///////////////////////
//
// As you can only assign lvalues (like variables, objects and other references)
// to references, C++11 introduces rvalue references for the others (like
// temporaries and literals). I'm not going to get into the details here as
// those subjects require whole documents to themselves. So you can have:
int aValue( 9 );
int& myPreferenceIsAReference( aValue );
int& alsoReferingToAValue( myPreferenceIsAReference );
// But you can't have:
// MyEnumClass::MyHelpfulEnum& isTheCodeClear( MyEnumClass::KeepCodeClear() );
// as the return from MyEnumClass::KeepCodeClear() is a temporary variable.
//
// In C++11 using rvalue references you can:
MyEnumClass::MyHelpfulEnum&& isTheCodeClear( MyEnumClass::KeepCodeClear() );
if ( isTheCodeClear == MyEnumClass::Usually )
{
std::cout << "isTheCodeClear == MyEnumClass::Usually, must do better!"
<< std::endl;
}
// You can also do things that seem very strange.
int&& rValueRef = 2;
// assign the rvalue reference to a reference to an rvalue, in this case the
// literal number 2.
++rValueRef;
// Has this blown your mind? We have incremented 2! Surely all that we know and
// love is doomed. It's ok though, as rValueRef is assigned a reference to a
// temporary variable, this is saving time rather than creating a temporary
// and then copying it to a variable.
std::cout << "rValueRef referencing the literal number 2 = "
<< rValueRef << " OMG!" << std::endl;
// These two examples show that less copying of values is attained by using rvalue
// references. The C++11 standard libraries and STL have been written to take
// advantage of this to improve performance without requiring users of these
// libraries to do anything different (apparently). There's lots of talk about
// 'move semantics' and 'perfect forwarding' which is nice if you like that kind
// of thing, these subjects require blogs of their own.
// In summary, they allow less copying, making copy constructors and equals
// operators more efficient. This in turn helps developers to write logical
// code that is far less likely to cause performance issues.
// 1. (again) static_assert
///////////////////////////
//
// static_assert (for compile time assertions). This time we are using the
// static_assert feature to check the sanity of a constant value against another
// constant. This is especially useful to stop people changing constants to
// erroneous values.
const int someConfigValue = 1;
const int someConfigValueCheck = 2;
// As before in the template example, if the condition is false the compiler
// outputs the error text supplied.
static_assert( someConfigValue < someConfigValueCheck,
"someConfigValue is not less than someConfigValueCheck!" );
TemplateTest<int> inttest;
// This is fine, an int passes the static_cast in TemplateTest.
// TemplateTest<bool> booltest;
// This generates a compile time error due to the static cast in the template
// class defined above.
// 9. nullptr
/////////////
//
// nullptr is more strongly typed than 0 or NULL, which helps make things clearer.
// nullptr is not available on GCC at the time of writing so comment out the rest
// of this example to compile with GCC.
int* nullptrtest( nullptr );
if ( nullptrtest == nullptr )
{
// the pointer is null.
std::cout << "The nullptrtest null pointer is set to nullptr."
<< std::endl;
}
// If we don't use nullptr, which function is called for our old null pointer
// values?
NullPointerCall( 0 );
NullPointerCall( NULL );
// If we use nullptr instead things are clearer.
NullPointerCall( nullptr );
// 10. initialiser lists
////////////////////////
//
// This is supported by GCC, but not VS, so I've commented it out. Obviously
// initialiser lists can also be used to pass data to methods. I think this is one
// of the more useful features of C++11, if only for reasons of clarity.
//std::map< int, int> myuselessnumbermap{ { 1,1 }, { 2, 2 } };
// There are many other C++11 features that haven't made it into stable versions
// of either VS or GCC, but now that the standard has been ratified I expect they
// will soon follow. Better enum support, in-class member variable initialisation,
// override control and thread support like thread local storage are all useful
// features, there are many more new features that future compilers will support.
// See http://www2.research.att.com/~bs/C++0xFAQ.html for a description of these
// and the unimplemented C++11 features (aka C++0x, they must've been a bit late).
return 0;
// Literals in code!! Just this one time I'll let myself off (anyway, I've broken
// most of our other coding standards along the way).
}
// These are the implementations of the nullptr description function calls.
void NullPointerCall( int notAPointerParameter )
{
std::cout << "So you'd like me to process your integer?" << std::endl;
}
void NullPointerCall( int* aPointerParameter )
{
std::cout << "So you'd like me to process your pointer?" << std::endl;
}
