Modern C++ With Examples

The programming language C++ originated in the early 80s and made slow but not stellar improvements, adding in a standard library and standard template library (STL). Indeed for many years some C++ programmers preferred the old C style strings rather than the new C++ std::string type. There was a major C++ release in 1998, then 2003 but it wasn’t until C++ 11 that C++ really started changing. Since then C++ 14 has come along but that’s a minor release by comparison to C++ 11.

C++ 11

Even the creator of C++ Bjarne Stroustrup considers that C++ 11 feels like a new language. For me one of the minor yet useful improvements was the use of auto to declare variables instead of declaring the actual type.

auto y=0; //y is type int because 0 is int

auto d=’c’; //char

auto d=0.9; //double

Other programming languages have this for instance C# with var. It simplifies the code as well and makes it easier to read and understand. Note C and pre C++ 11 used auto in a different sense. The meaning has changed.

Lambda Expressions

It sounds complicated but it simply means you use a local anonymous function instead of calling one. This example uses a Lambda Expression to count the number of uppercase letters in a string. Unsurprisingly if you run this it finds there are two uppercase letters.

 

#include <string>

#include <iostream>

#include <algorithm>

int main()

{

std::string s = “Middle Earth”;

auto Count = 0;

std::for_each(s.begin(),s.end(), [&Count](char c) {

if (isupper(c))

{

Count++;

}

});

 

std::cout <<“There are ” << Count << ” uppercase letters in: ” << s << std::endl;

}
Lambdas make it easy to apply a function to a collection of objects. It’s a big step in the direction of functional programming and makes programs more robust.

RValues and Move Semantics

In the code below

 

auto c = a * b,

 

c is an lvalue. It’s on the left hand side of the =. While a * b is on the right hand side and is an rvalue. Every expression yields either an lvalue or a rvalue. If an expression is not an lvalue then it’s a rvalue. A better way of distinguishing the two is lvalue means locator value- it’s the address of the memory where the value is stored. The rvalue is the value of the expression. There’s a bit more subtlety when it concerns objects, you can read more about this here.

This is relevant when it comes to move semantics. Before C++ 11 cloning an object which contained a resource was like Star Trek teleportation. The original object was destroyed and a new one created. But if a resource is non-trivial then destroying and creating it takes time.

Assigning an object to another is a similar case. You copy the resource then destroy the original. Why not just swap a reference to the original and give it to the new object. This is what move semantics is about and it was introduced in C++ 11. Now 8 years old but worth a read is the intro to rvalue references.

Better still to just give the pointer to the object to the new owner and null without destructing the original pointer. Instead of copying we effectively just change ownership. That gives performance boosts in many cases. This is the Explain Like I’m Five version. In practice there are a few subtleties and you should familiarise yourself with std::move.

NullPtr and Smart Pointers

A hangover from C, the value 0 was used to mean a pointer that points to nothing. This can confuse the compiler, if there are two overloads to a function, distinguished by the function parameters one with an int and one with a pointer then passing 0 as the parameter does not help the compiler decide which overload you meant.

C++ 11 introduced nullptr which resolved this issue. If you now pass 0 it means the int value. Pass nullptr and it means the pointer version. So instead of comparing if a pointer has a value by doing if (!ptr) you should do if (ptr == nulptr).

C++ 98 introduced an auto_ptr which has now been dropped. Instead there are shared_ptrs and weak_ptrs introduced in C++ 11. A weak_ptr is a template class that manages a pointer to an object. If that pointer is destroyed it does not destruct the object that it pointed to. That is why it’s a weak_ptr. It does not own the object it points to.

A shared_ptr manages a pointer with reference counting. Only when all references to the object have been destroyed can the object be destroyed.

Finally there’s a unique pointer type unique_ptr. The unique pointer object cannot be copied because its copy constructor and assignment operator have been deleted to prevent copying. You can use std::move the value between different unique_ptrs.

 

std::unique_ptr<int> p1(new int(25)); // p1 is a unique_ptr that points to an int with the value 25.

 

std::array

Inherited into C++ from C, the array type is inflexible, you can’t resize it. For many years people have used std::vector to do a single dimensional array that can be resized. In C++ 11 the type std::array<type> was introduced so in theory you never have to use old style arrays ever again.

However the old style arrays make it a bit simpler to declare static data. It’s quite clumsy using std_array to do that. Also declaring multi-dimensional arrays is simpler with C style arrays as the example below shows.

char oldstyle[3][3]; // the old vs

std::array<std::array<int, 3>, 3> newstyle;  // the new!
But performance wise, the std::array should be as quick as the old style.

Conclusions

I found that writing C++ 11 programs was a little simpler and it felt a cleaner style.  Given that all the major C++ compilers are C++ 11 compatible it’s no great thing to start switching.

Post a Comment

Your email address will not be published.