Modern C++ at a Glance — A Language a Day, Advent Calendar 2019 Day 5/24

About this ‘A Language a Day’ Advent Calendar 2019

Welcome to Day 5 of this year’s A Language a Day Advent Calendar. Today’s topic is an overview of the very-well updated C++ programming language.

C++ feels like a new language
B. Straustrup, A Tour of C++

You may reasonably ask what’s up with C++? It appeared about 35 years ago. Yes and no. In 2011, the new era of C++ began. If you did not follow its development during the last 5-10 years, you would be surprised how the language has developed.

Facts about the language

  • It is a compiled language
  • Based on C but goes far beyond
  • Designed to have a zero overhead (no extra code generated for the features that are not directly used)
  • Provides strong object-oriented support
  • Released in 1985, “reinvented” in 2011
  • Website: isocpp.org

Installing and running C++

Most likely, you already have the GCC compiler installed on your machine if you are working in the Linux environment. Otherwise, install it from packages. For a Mac, you can either download the complete XCode or its command-line tools, XCode Developer Tools.

There are a number of other compilers in the wild, and you can choose your most beloved. The rest of the article is using the Gnu’s GCC C++ compiler:

$ g++ hello-world.cpp -ohello-world
$ ./hello-world 

To force the standard, reference it in the -std parameter, for example:

$ g++ -std=c++11 auto.cpp

You can use c++11, c++14, c++17 values for the corresponding standards, and c++2a for the working-in-progress C++ 2020.

Hello, World!

#include <iostream>

int main() {
    std::cout << "Hello, World!\n";
}

There are two things to note here (although they were available long time ago already). First, no .h extension in the header file. Second, it is officially fine to omit returning the value from the main function.

Variables

The compiler can now deduce the type of the variable itself. All you need to do is to use the auto keyword in place of the type of your variable.

#include <iostream>

int main() {
    auto name = "John";
    std::cout << "Hello, " << name << "!\n";
}

Functions

In C++ 14, type deducing is also available for the return values of functions:

#include <iostream>

auto sqr(int x) {
    return x * x;
}

int main() {    
    std::cout << sqr(5) << std::endl;
}

To compile this program, use the -std=c++14 (or higher version) option.

Lambdas

C++ now provides the syntax to create lambda functions. Take a look at the following example. In it, we pass our own predicate to the sort function from the standard library. Instead of creating a stand-alone function, you can define it directly at the place where it is used.

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int main() {
    vector<int> data {10, 4, 2, 6, 2, 7, 1, 3};

    sort(data.begin(), data.end(), [](int a, int b){return b < a;});

    for (auto x : data) cout << x << endl;
}

The highlighted code is an anonymous two-argument function returning a Boolean value. This is what the third optional parameter of sort expects. (This is an example, and you can use the std::greater function instead.)

Containers

When you are working with the standard library, say, with its containers, you had to type a lot in the past. Compare the following two versions of a program that prints three strings from a vector.

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<string> v;
    v.push_back("alpha");
    v.push_back("beta");
    v.push_back("gamma");

    for (vector<string>::const_iterator i = v.begin(); i != v.end(); i++)
        cout << *i << "\n";
}

Here, you had to manually fill the vector with your data, and to deal with an explicit iterator, whose type is a long string of text, which is already shortened by using the namespace in this program. Without that, it would be std::vector<std::string>::const_iterator.

You do it much simpler today:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<string> v {"alpha", "beta", "gamma"};

    for (auto s : v) cout << s << "\n";
}

Modern C++ allows initializer lists, and you can set initial values to a container similarly to how you do it for arrays. The for loop here is the so-called range-for loop. Together with auto, it does all the work for you regardless of the actual type of the container and its contents (while still being a fully statically typed construct).

Classes

C++’s object-oriented model is one of the best known among developers. As this article is primarily devoted to the new versions of C++, let me directly go to the code.

A Polymorphic example

Here is the zoo of cats and dogs.

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Animal {
public:
    virtual string info() = 0;
};

class Cat: public Animal {
public:
    virtual string info() {
        return "Cat";
    }
};

class Dog: public Animal {
public:
    virtual string info() {
        return "Dog";
    }
};

int main() {
    vector<Animal*> zoo {new Cat(), new Cat(), new Dog(), new Dog()};
    for (auto x : zoo) cout << x->info() << "\n";
}

The Animal class is a base of both Dog and Cat, and it has a pure virtual method info() returning a string. The virtualness is obviously stated by the virtual keyword, and =0 is what makes it a pure virtual method.

Concurrency and threads

Modern C++ directly supports threads. It does it via the standard library, so you can use it on many platforms. Syntactically, this is a standard C++, but for a foreigner it may looks like syntax sugar to hide multitasking management behind a few keywords.

Sleep Sort

The following program is a complete implementation of the Sleep Sort algorithm. It creates a thread for each number, and then joins them. The thread executes a function that sleeps and prints the number.

#include <iostream>
#include <vector>
#include <thread>

using namespace std;

void sort_me(int n) {
    this_thread::sleep_for(chrono::milliseconds(n));
    cout << n << endl;
}

int main() {
    vector<int> data {10, 4, 2, 6, 2, 7, 1, 3};

    vector<thread> tasks;
    for (auto n : data) tasks.push_back(thread {sort_me, n});
    for (auto&& t : tasks) t.join();
}

Notice the two ranged-base for loops in the main function. In the first of them, a pointer to a new thread is saved in the tasks vector. In the second, this vector is looped through to join all the threads before quitting the program.

Thread-safety

There is a small problem with the above-shown Sleep Sort code: its output is not thread-safe here. If you have smaller delays (change it to microseconds, for example) and bigger numbers to sort, you may see that the newlines and maybe even different digits are sometimes printed at wrong times. To prevent this, add a mutex:

#include <mutex>

. . .

void sort_me(int n) {
    static mutex mu;

    this_thread::sleep_for(chrono::milliseconds(n));
    
    unique_lock<mutex> lock {mu};
    cout << n << endl; // thread-safe now
}

Notice that using the unique_lock template helps you to lock and release the mutex with a single line of code. The release happens in the destructor of the lock object, which is called at the end of its scope, which is the sort_me function.

Get more

C++ is a great language and I hope that if you used to use it a decade ago, you may reconsider using it again. Use the following resource to learn more about the modern versions of the language.

The source codes for today’s article are on GitHub.

Next: Day 6. Crystal

2 thoughts on “Modern C++ at a Glance — A Language a Day, Advent Calendar 2019 Day 5/24”

Leave a Reply

Your email address will not be published. Required fields are marked *