So, you came across the Modern C++ & overwhelmed by its features in terms of performance, convenience & code expressiveness. But in a dilemma that how you can spot where you can enforce Modern C++ features in your day to day coding job. No worries, here we will see 21 new features of Modern C++ you can use in your project.
C++ community releasing new standards more frequently than iPhone releases. Due to this, C++ now becomes like an elephant and it is impossible to eat the whole elephant in one go. That is why I have written this post to kick start your Modern C++ journey. Here my intended audience is peeps who are moving from older(i.e. 98/03) C++ to Modern(i.e. 2011 onwards) C++.
I have chosen some of the Modern C++ features & explained it with the minimalistic example to make you aware that how you can spot the places where you can employ new features.
Digit separators
- Earlier you have to count digits or zeros, but now not anymore from C++14.
- This will be useful while counting address in word, half-word or digit boundary or let say you have a credit card or social security number.
- By grouping digits, your code would become more expressive.
Type aliases
- Semantically similar to using a
typedef
, however, type aliases are easier to read and are compatible with C++ templates types also. Thanks to C++11.
User-defined literals
- Most of the times you have to deal with real-world jargons like KB, MB, km, cm, rupees, dollars, euros, etc. rather defining functions which do the unit conversion on run-time, you can now treat it as user-defined literals as you do with other primitive types.
- Very convenient for units & measurement.
- Adding constexpr will serve zero cost run-time performance impact which we will see later in this article & I have written a more detailed article on when to use const vs constexpr in c++.
Uniform initialization & Non-static member initialization
Earlier, you have to initialize data members with its default values in the constructor or in the member initialization list. But from C++11, it’s possible to give normal class member variables (those that don’t use the static
keyword) a default initialization value directly as shown below:
|
|
- This is more useful when there are multiple sub-objects defined as data members as follows:
- In this case, you do not need to initialize it in initializer list, rather you can directly give default initialization at the time of declaration.
- You can also provide initialization at the time of declaration if members are
const
&static
as above.
std::initializer_list
|
|
- Assign values to containers directly by initializer list as do with C-style arrays.
- This is also true for nested containers. Thanks to C++11.
auto & decltype
auto
-typed variables are deduced by the compiler according to the type of their initializer.- Extremely useful for readability, especially for complicated types:
- Functions can also deduce the return type using
auto
. In C++11, a return type must be specified either explicitly, or usingdecltype
like:
- Defining return type as above called trailing return type i.e.
-> return-type
.
Range-based for-loops
- Syntactic sugar for iterating over a container’s elements.
- Note the difference when using
int
as opposed toint&
:
Smart pointers
- C++11 introduces new smart(er) pointers:
std::unique_ptr
,std::shared_ptr
,std::weak_ptr
. - And
std::auto_ptr
now become deprecated and then eventually removed in C++17.
- ISO CPP guidelines suggest avoiding the call of
new
anddelete
explicitly by the rule of no naked new. - I have already written an article on understanding unique_ptr with example in C++ here.
nullptr
- C++11 introduces a new null pointer type designed to replace C’s
NULL
macro. nullptr
itself is of typestd::nullptr_t
and can be implicitly converted into pointer types, and unlikeNULL
, not convertible to integral types exceptbool
.
Strongly-typed enums
- Type-safe enums that solve a variety of problems with C-style enums including implicit conversions, arithmetic operations, inability to specify the underlying type, scope pollution, etc.
Typecasting
- C style casting only change the type without touching underlying data. While older C++ was a bit type-safe and has a feature of specifying type conversion operator/function. But it was implicit type conversion, from C++11, conversion functions can now be made explicit using the
explicit
specifier as follows.
- If the above code looks alien to you, I have written a more detailed article on C++ typecasting here.
Move semantics
- When an object is going to be destroyed or unused after expression execution, then it is more feasible to move resource rather than copying it.
- Copying includes unnecessary overheads like memory allocation, deallocation & copying memory content, etc.
- Consider the following swap function:
- using move allows you to swap the resources instead of copying them around:
- Think of what happens when
T
is, say,vector<int>
of size n. And n is too big. - In the first version, you read and write 3*n elements, in the second version you basically read and write just the 3 pointers to the vectors’ buffers, plus the 3 buffers’ sizes.
- Of course, class
T
needs to know how to do the moving; your class should have a move-assignment operator and a move-constructor for classT
for this to work. - This feature will give you a significant boost in the performance which is why people use C++ for(i.e. last 2-3 drops of speed).
Forwarding references
- Also known (unofficially) as universal references. A forwarding reference is created with the syntax
T&&
whereT
is a template type parameter, or usingauto&&
. This enables two major features- move semantics
- And perfect forwarding, the ability to pass arguments that are either lvalues or rvalues.
Forwarding references allow a reference to binding to either an lvalue or rvalue depending on the type. Forwarding references follow the rules of reference collapsing:
T& &
becomesT&
T& &&
becomeT&
T&& &
becomesT&
T&& &&
becomesT&&
Template type parameter deduction with lvalues and rvalues:
|
|
- If this seems complex & weird to you then read this first & then come back here.
Variadic templates
- The
...
syntax creates a parameter pack or expands one. A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A C++ template with at least one parameter pack is called a variadic template.
constexpr
- Constant expressions are expressions evaluated by the compiler at compile-time. In the above case,
fibonacci
the function is executed/evaluated by the compiler at the time of compilation & result will be substituted at calling the place. - I have written a detailed article on when to use const vs constexpr in C++.
Deleted & Defaulted functions
- Now you might be wondering that rather than writing 8+ letters(i.e.
= default;
), I could simply use {} i.e. empty constructor. That’s true! but think about copy constructor, copy assignment operator, etc. - An empty copy constructor, for example, will not do the same as a defaulted copy constructor (which will perform a member-wise copy of its members).
You can limit certain operation or way of object instantiation by simply deleting the respective method as follows
In older C++ you have to make it private. But now you have delete
compiler directive.
Delegating constructors
- In older C++, you have to create common initialization member function & need to call it from all the constructor to achieve the common initialization.
- But from C++11, now constructors can call other constructors in the same class using an initializer list.
Lambda expression
- I think this feature no need any introduction & hot favourite among other features.
- Now you can declare functions wherever you want. That too with zero cost performance impact.
- I wrote a separate article to learn lambda expression in C++ with example.
Selection statements with initializer
- In earlier C++, the initializer is either declared before the statement and leaked into the ambient scope, or an explicit scope is used.
- With C++17, the new form of if/switch can be written more compactly, and the improved scope control makes some erstwhile error-prone constructions a bit more robust:
- How it works
std::tuple
- Tuples are a fixed-size collection of heterogeneous values. Access the elements of a
std::tuple
by unpacking usingstd::tie
, or usingstd::get
. - You can also catch arbitrary & heterogeneous return values as follows:
- Use
std::ignore
as a placeholder for ignored values. In C++17, structured bindings should be used instead.
Class template argument deduction
- Automatic template argument deduction much likes how it’s done for functions, but now including class constructors as well.
Closing words
Here, we have just scratched the surface in terms of new feature & the possibility of its application. There are many things to learn in Modern C++, but still, you can consider this as a good starting point. Modern C++ is not only expanding in terms of syntax but there is lot more other features are also added like unordered containers, threads, regex, Chrono, random number generator/distributor, exception handling and many new STL algos(like all_of()
, any_of()
and none_of()
, etc).
Happy Modern C++ Coding…!