Skip to content

Robomongo Cplusplus 11, 14 Transition Guide

Gökhan Simsek edited this page Jun 30, 2017 · 14 revisions

This guide attempts not only to present some of the key new C++11/14 features but also to help updating existing old code to modern C++11/14 style with examples.

A. C++11 New Features & Transition Guide
B. C++14 New Features & Transition Guide
C. References


A. C++11 New Features & Transition Guide

There are fundamental changes in C++ with C++11 Major release. Here are some of the C++11 features that we use for new code and we incrementally refactor our existing code with.

1. Use smart pointer std::unique_ptr<T> instead of T* (owning pointer):

Compared to other C++11 features smart pointers have much more importance. Because with automatically deleted smart pointers, C++ finally avoids all the problems (forgot-to-delete memory leaks, exception caused memory leaks, double deletion crashes etc..) caused by old C-style code (new/delete).

std::unique_ptr<T> is the default choice and replacement for T* (owning pointer) but other smart pointers (shared_ptr, weak_ptr) can also be used in appropriated cases.

Important Note:
std::unique_ptr<T> (and other smart pointers) are created to replace T* (owning pointer) not T* (non-owning aka raw pointer). Usage of T* (non-owning aka raw pointer) is still absolutely valid and suggested. More: CppCon 2014: Herb Sutter "Back to the Basics! Essentials of Modern C++ Style", 12:09

a) Feature

// C++11
std::unique_ptr<DBClient> _dbclient { new DBClient };   
    // Automatically deleted (even in exception cases)

// Before C++11
DBClient* _dbclient = new DBClient;                   
    // Must be manually deleted by programmer (easy to forget, leads to memory leak)
    // Crash when deleted more than once
    // Memory leak if exception is thrown before line of 'delete _dbclient;'

b) Usage & How to change existing code

// C++11  
DBClient* getClient() const { return _dbclient.get(); } 
    
// Before C++11          
DBClient* getClient() const { return _dbclient; }      
// C++11  
_dbclient.reset(new DBClient);
    
// Before C++11          
delete _dbclient;
_dbclient = new DBClient;       

2. New range-for: for(auto const& name : _names)

// C++11
for (auto const& database: _databases)
    database.backup(); 

// Before C++11
for (std::vector<Database*>::const_iterator itr = _databases.begin(); itr != _databases.end(); ++itr)
    itr->backup();

3. auto keyword for automatic type deduction

// C++11  
auto itr = _databases.begin()
    // 'itr's type automatically deduced as 'std::vector<Database*>::const_iterator'    

// Before C++11          
std::vector<Database*>::const_iterator itr = _databases.begin()

Of course with auto the the range-for also becomes much shorter and easy to write.
Using the same example above:

// C++11
for (auto const& database: _databases)
    database.backup(); 
    // Note that simple 'auto const&' is actually much more powerful than it looks.
    // - 'auto' creates easily maintainable code with automatic type deduction  
    // - 'const' for correctness, prevents accidental changes to 'database' variable 
    //    and lets compiler to optimize code for faster compile & run-time
    // - '&' for performance, to avoid unnecessary copy of 'database'

// Before C++11
for (std::vector<Database*>::const_iterator itr = _databases.begin(); itr != _databases.end(); ++itr)
    itr->backup();

One warning about auto, over-using this feature might not be a very good idea since the type will not be visible to programmer, it will diminish code readability.
Example:

// Here it might be hard to read or debug this code if the function names 
// are not very clear

auto extraOptions = getExtraOptions();
auto engineType= getStorageEngineType();
...
createCollection(serverName, dbVersion, extraOptions, engineType);

4. nullptr, the pointer literal

Before C++11, NULL macro and 0 were used for null pointer value. Since NULL and 0 were not special pointer types, there were places where overload resolution of C++ might get confused and another problem with NULL macro was that it could be defined with different values.

To remove this confusion and problem new pointer literal nullptr is defined (type of std::nullptr_t).

From Bjarne Stroustrup's C++ Style and Technique FAQ:

In C++, the definition of NULL is 0, so there is only an aesthetic difference. I prefer to avoid macros, so I use 0. Another problem with NULL is that people sometimes mistakenly believe that it is different from 0 and/or not an integer. In pre-standard code, NULL was/is sometimes defined to something unsuitable and therefore had/has to be avoided. That's less common these days.

If you have to name the null pointer, call it nullptr; that's what it's called in C++11.

// C++11
    DBClient* dbclient = nullptr;

// Before C++11
    DBClient* dbclient = 0;
    DBClient* dbclient = NULL;
}

http:https://en.cppreference.com/w/cpp/language/nullptr


B. C++14 New Features & Transition Guide

Along with transition to MongoDB 3.4, we have updated our tool chain and it is now possible to use latest modern C++14 features in Robomongo code base. (Note: Theoretically, C++17 is the current latest C++ version, but we can say C++14 is the latest practically used and implemented by most compiler vendors.)

We are using following modern compilers for Windows, Linux and MAC OS in order:

  • Visual Studio 2015 Update 3
  • GCC 5.4.1
  • Clang version: Apple LLVM version 7.3.0 (clang-703.0.31)

Some of the C++14 features we will be able to use and plan to use are below:

a. Automatic return type deduction for functions

// In C++14: using 'auto' as function return type
auto const& getCollections() const 
{
    std::vector<MongoCollection> collections;    
    ...
    return collections;
}

// Before C++14
std::vector<MongoCollection> const& getCollections() const 
{
    std::vector<MongoCollection> collections;    
    ...
    return collections;
}

b. Binary, time and string literals

Binary literals:

    int binNumA = 0b01100001;
    int binNumB = 0b1001;

Time literals: (defined in #include <chrono>)

    auto sec = 42s;     // second
    auto msec = 42ms;   // millisecond
    auto usec = 42us;   // microsecond
    auto nsec = 42ns;   // nanosecond
    auto min = 42min;   // minute
    auto hour = 42h;    // hours

String literals:

    auto strA = "42"s;      // std::string
    auto strB = "42";       // const char*

c. Generic lambdas and initial values for lambda captures

With C++14 it is possible to write generic lambdas using auto with parameters which makes lambda generic to accept different type of arguments. Below the example lambda function saveEventId function uses "(auto const& event)" and we are able to process two different type of events with one generic lambda function.

    class TimerExpiredEvent;
    class BackupStartedEvent;    

     // C++14 generic lambda with "auto const& event"
    auto saveEventId = [](auto const& event) {
        // ...
        _eventIds.push_back(event.id());
    };

    saveEventId(timerExpiredEvent);
    saveEventId(backupStartedEvent);

Showing another example for setting initial values for lambda capture variables:

using namespace chrono::high_resolution_clock;  // for simplicity

// C++14 lambda function using 'now()' as initial value 
// for 'capturedTime'
auto elapsedTimeNs = [capturedTime = now()] () { 
    return (now() - capturedTime);
};

// Some operation to calculate its time  
runDatabaseBackup();

// Result in nanoseconds
std::chrono::nanoseconds elapsedTimeNanosec = elapsedTimeNs();

d. Digit separators

Important to note that Digit separators (single quote ' ) are designed for better readability, they are not visible to the compiler.

    // Decimal - following two values are identical to the compiler
    int x = 1'000'000;      
    int y = 1'000'0'00;

    // Binary
    int binNumA = 0b0110'0001;
    int binNumB = 0b10'00'10'01;

    // Hex
    auto hexA = 0x1001'0000'1011;  // deduced as long long
    auto hexB = 0x1010'1001;       // deduced as int

C. References

C++ Core Guidelines by Bjarne Stroustrup, Herb Sutter, June 8, 2017

Scott Meyer's guideline-based books on C++ (http:https://www.aristeia.com/)

  • Effective Modern C++, Scott Meyers, 2014
  • Effective C++, Third Edition, 2005
  • Effective STL, 2001
  • More Effective C++, 1996

C++ Coding Standards: 101 Rules, Guidelines, and Best Practices Nov 4, 2004
by Herb Sutter and Andrei Alexandrescu