-
Notifications
You must be signed in to change notification settings - Fork 797
Robomongo Cplusplus 11, 14 Transition Guide
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
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.
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://en.cppreference.com/w/cpp/language/nullptr
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++ Core Guidelines by Bjarne Stroustrup, Herb Sutter, June 8, 2017
Scott Meyer's guideline-based books on C++ (http://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