J@ArangoDB

{ "subject" : "ArangoDB", "tags": [ "multi-model", "nosql", "database" ] }

What I Most Like About C++11

About half a year ago we started compiling our code with -std=c++11.

We had to fix a few, but not too many code parts for this. That was the easy part.

Getting C++11 to work on all supported platforms, build and testing environments was a bit more challenging, but we finally managed to do it.

Having used C++11 for some time now, I think it’s time to share a few of improvements in C++11 that solve common problems.

First of all, I don’t regret we changed to it. In my opinion, C++11 makes coding easier and safer. I will try to demonstrate that with a few examples in a second.

Before I go into details, just let me state that I will only show a few of my personal favorites here. There are so many more improvements in C++11 that are all worth having a look. If you haven’t looked into C++11 much, I recommend getting started at the Wikipedia page about C++11.

Auto

From a developer perspective, one of the most compelling features of C++11 is the revamped auto keyword. Consider the following C++98/C++03 code:

C++03 version
1
2
3
4
std::map<std::string, std::string>::iterator it = resultHeaders.find(value);
if (it != resultHeaders.end()) {
  // do something with value
}

In C++11 this code can be simplified to:

C++11 version with auto
1
2
3
4
auto it = resultHeaders.find(value);
if (it != resultHeaders.end()) {
  // do something with value
}

In the C++11 version of the code, the compiler can figure out the type of variable it all by itself. This allows writing less (i.e. better) code.

The more complex the types are, the more helpful this gets. Compare the following two lines and check for yourself which one you prefer:

C++03 version
1
std::map<CollectionID, std::shared_ptr<std::vector<std::string> > >::iterator it = shards.find(collectionID);
C++11 version with auto
1
auto it = shards.find(collectionID);

auto provides an extra benefit: when using auto it is not necessary to repeat the type information throughout the code. This is helpful when types need to be changed and the change needs to be reflected everywhere. With auto, chances are that less code needs to be adjusted. And it is not necessary to set up extra typedefs for this.

If you think auto obfuscates the meaning too much, you can be a bit more expressive, e.g.

C++11 version with auto, const reference
1
2
3
4
auto const& it = resultHeaders.find(value);
if (it != resultHeaders.end()) {
  // do something with value
}

Range-based loops

We all have written a lot of code that iterates over a ranges, like this:

C++03: iterating over a range
1
2
3
4
std::map<string, AgencyCommResultEntry>::iterator it;
for (it = result.values.begin(); it != result.values.end(); ++it) {
  // do something with it
}

C++11 provides a special range-based syntax for for loops, which makes this a lot easier and compact:

C++11: iterating over a range
1
2
3
for (auto it : result.values) {
  // do something with it
}

Decltype

As we have seen, the compiler can deduce the type of expressions automatically. C++11 also allows using this type information with the decltype keyword. This allows to write more generic and maintainable code.

In the following code, the type of variable document is a pointer to a Document. Variable myDocument has the same type:

C++03 explicit type specification
1
2
Document* document = res->getDocumentCollection(registerId);
Document* myDocument = document;

In C++03, we couldn’t tell the compiler that the two variables should always have the same types. In C++11, we can explicitly give myDocument the same type as document, without any typedefs:

C++11 automatic type deduction
1
2
3
```c++ C++11 automatic type deduction
auto* document = res->getDocumentCollection(registerId);
decltype(document) myDocument = document;  // myDocument has same type as document

decltype can also be used to deduce the type of expressions.

Lambdas / Closures

Lambdas are available in most other mainstream languages today, and they are available in C++11, too.

Probably one of the most common use cases for a lambda is a custom comparator function for sorting:

custom comparator function using a lambda
1
2
3
4
5
std::sort(operations.begin(),
          operations.end(),
          [] (Operation const* left, Operation const* right) {
  return (left->id < right->id);
});

In the above example, the lambda has two input parameters and produces a boolean result. Note that the type of the result was not explicitly specified. Again the compiler is able to figure it out automatically.

A lambda can be assigned to a variable, and it can be passed as a parameter to another function/method. Lambdas can optionally have access to the variables of the scope they were created in.

The following code defines a struct ScopeGuard that executes a lambda in its constructor and another lambda in its destructor:

lambdas as function parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// define ScopeGuard struct
struct ScopeGuard {
  ScopeGuard (std::function<void()> onEnter,
              std::function<void()> onExit)
    : onExit(onExit) {
      onEnter();
  }

  ~ScopeGuard () {
    onExit();
  }

  std::function<void()> onExit;
};

// lambda to be executed in constructor
auto onEnter = [&engine]() -> void {
  engine->getQuery()->enterContext();
};

// lambda to be executed in destructor
auto onExit = [&]() -> void {
  for (auto expression : allVariableBoundExpressions) {
    expression->invalidate();
  }
  engine->getQuery()->exitContext();
}

// create guard object with the lambdas 
// this will instantly execute `onEnter`
ScopeGuard guard(onEnter, onExit);

// do something...

// when scope is left, `onExit` will be executed

As mentioned before, decltype can be used to determine the return type of a function automatically. Here’s an example:

function with automatic return type deduction
1
2
3
auto add = [](int a, int b) -> decltype(a + b) {
  return a + b;
};

As can be seen in the examples above, C++11 has introduced an alternative function declaration syntax, with the type of the function result following a ->. The return type can be omitted if it can be unambiguously determined by the compiler. The new function declaration syntax is mainly useful for lambdas, but it can be used for regular functions, too.

Enum class

Enums in C++ are useful but just don’t feel right: persisting a struct that contains an enum value is not portable as the underlying data type for the enum is implementation-dependent.

Additionally, enum values can be compared to almost any other values, which in most cases doesn’t make sense but obfuscates coding errors. There were also scoping issues with enum values.

C++11 enum classes fix these problems. First of all, the underlying data type for an enum can be specified. For example, this creates an enum based with its value stored in an std::uint8_t:

enum class with specified data type
1
2
3
4
5
6
7
8
9
enum class StatusType : std::uint8_t {
  UNINITIALIZED = 0,
  STARTING,
  RUNNING,
  STOPPING,
  STOPPED
};

StatusType status;

Regarding the comparison of enum values to other values, C++11 enum classes are much stronger typed than regular enums. Comparing the status variable from the above example to anything but a value from its enum class won’t even compile.

This provides much greater type safety than when using the old, implicitly converting enums:

invalid usage of enums
1
2
3
4
5
6
if (status == 0) {  // won't compile in C++11
  // ...
}
if (status == StatusType::UNINITIALIZED) {  // this would work
  // ...
}

Finally, enum classes fix the scoping problems of regular enums. In C++03, the following code did not complile because two enums contained the same member name:

two enums with same member name
1
2
3
4
5
6
7
8
9
enum DirectionType {
  LEFT,
  RIGHT
};

enum AnswerType {
  RIGHT,  // won't compile in C++03 and C++11
  WRONG
};

With C++11 enum classes, the following code is all fine:

two enums class with same member name
1
2
3
4
5
6
7
8
9
enum class DirectionType {
  LEFT,
  RIGHT
};

enum class AnswerType {
  RIGHT,  // works!
  WRONG
};

Additional containers

C++11 provides the hash-based containers std::unordered_map and std::unordered_set (plus their non-unique counterparts). These containers are not sorted, so they can be more efficient than std::map and std::set.

Turning an std::map into an std::unordered_map is simple as the APIs are more or less identical.

There is now also a singly-linked list container, named std::forward_list. This obviously allows forward iteration only, but is more space efficient than the already existing doubly-linked list container.

More

Other improvements include move semantics, atomic variables and operations, a dedicated type for NULL pointers, STL support for threads and mutexes, regular expressions, more Unicode support, override, final – to name only a few…