Reminiscing
For many years (a long time ago in a galaxy far away) I was a C++ developer, but I haven’t done much in C++ for a while now, until recently…
I needed to revisit a C++ application I wrote (not quite so long ago) to make a few additions and at the same time I thought it worth reviewing the changes that have taken place in C++ in recent years. This post is primarily looking at some of the changes as part of C++ 11 that I’ve come across whilst updating my app. and whilst not comprehensive, should give a taste for “Modern C++”.
auto is to C++ what var is to C#
auto i = 1234;
This is resolved to the type at compile time (i.e. it’s not dynamic). The compiler simply resolves the type based upon the value on the right hand side of the assignment operator.
decltype
template<typename A, typename B>
auto f(A a, B b) -> decltype(a + b)
{
return a + b;
}
We can also use it in a sort of dynamic typedef manner
decltype(123) v = 1;
// equivalent to
int v = 1;
decltpye(123) declares the type as an int (as the type is taken from the argument passed in) then the value 1 is assigned to the variable.
for (each) loops known as range based loops in C++
The for loop (in a similar way to Java uses it) can be used as a foreach style loop. Although C++ calls this a range based loop. It only works on arrays or objects which have begin and end member functions (or initializer lists)
for(auto &it : myvector)
{
}
// using initializer list
for(auto i : { 1, 2, 3})
{
}
nullptr takes over from NULL
The nullptr is a strongly typed keyword which can replace those NULL or 0 usages when setting or testing a variable to see whether it points at something or not.
if(errors == nullptr)
{
}
Another benefit of the keyword being strongly typed is that in situations when we might have two methods (with the name overloaded) one taking and int, the second a pointer type, then the compiler can now correctly choose the pointer overload, for example
void f(int i)
{
}
void f(int* i)
{
}
f(NULL); // calls f(int i)
f(nullptr); // calls f(int* i)
So obviously the use of the NULL suggests the developer wants to use f(int*), but this is ambiguous and both could be correct. In the case of Visual Studio f(int) is called when we use NULL.
Lamda’s as well
I’ve written a longer post on lambda’s which will be published after this post. However, here’s a taster showing a lamda used within the algorithm header’s std::for_each function
std::for_each(v.begin(), v.end(), [](int i)
{
std::cout << i;
});
In this example, v is a vector of ints.
The lambda is denoted with the [] which captures variables from the calling code (in this case nothing is captured) and the rest is probably self-explanatory, we’ve created a lambda which takes and int and uses cout to write the value to the console.
Initializer lists
Like C# we can create lists using { } syntax, for example
std::array<int, 3> a = { 1, 2, 3 };
// or using a vector
std::vector<int> v = { 1, 2, 3 };
Sadly, Visual Studio 2015 doesn’t appear to fully support this syntax and allow us to easily intialize vectors etc. but CLion does. But thankfully Visual Studio 2017 does support this fully.
Note: Visual Studio 2015 accepts using std::vector v = { 3, (1,2,3) }; where the first number is the size of the vector.
Non-static member initializers
class FileData
{
public:
long a {0};
long b {1};
}
Upon creation of a FileData instance, a is initialized to 0 and b is initialized to 1.
Tuples
We can now create tuples by include the tuple header and creating a tuple using the std::make_tuple function, i.e.
auto t = std::make_tuple<int, std::string>(123, "Hello");
To get at each part of the tuple we can use either the get function or the tie function, like this
std::cout
<< std::get<0>(t)
<< std::get<1>(t)
<< std::endl;
Note: the index goes in the template argument.
Or using tie
int a;
std::string b;
std::tie(a, b) = t;
std::cout
<< a
<< b
<< std::endl;
std::thread, yes threads made easier
Okay, we’re not talking Task or async/await capabilities like C# but the std::thread class makes C++ threading that bit easier.
#include <thread>
#include <iostream>
void f(std::string s, int n) {
for (int i = 0; i < n; i++)
std::cout << s.c_str() << std::endl;
}
int main()
{
std::thread thread1(f, "Hello", 10);
std::thread thread2(f, "World", 3);
thread1.join();
thread2.join();
return 0;
}
In this code we create a thread passing in the function and the arguments for the function, using join to wait until both threads have completed.
References
Modern C+ Features
Support For C++11/14/17 Features (Modern C++)