Comparing C++11, C++14, C++17 and C++20






Since C++ was first introduced and initially standardized in 1998 as ISO/IEC 14882:1998, after which it was followed by many of its revisions. It was amended with time with some feature changes, new additions, and old unused feature removal were done to it and its new standards were released time to time as C++03, C++11, C++14, and C++17. The latest standard as of today is C++20 with additional features and more enriched library support.
Let's check some details about its differet versions.

C++ 11

The initial version of C++ (C++03) was superseded by C++11 and approved by ISO on 12th August 2011. C++11 introduced several additions to the core language. Changes were focused on various areas like improving multithreading support, uniform initialization, and amendment in C++ Standard Library.
Let's check some of the changes that were introduced on C++11.

  • Lambda Functions :   C++11 provides the option to create anonymous functions which are known as lambda functions. Syntax for lambda function is:

                        
                          [capture](function parameters) -> return_type { function_body }
                        
                      

    Example of a lambda function is :

                        
                          [ ](int param1, int param2) -> int { return param1 + param2; }
                        
                      

    In the Lambda expression C++ allow us to define the " closures or captures " between the "[" and "]". This will allows these variables to be captured by value or by reference. Below are the some details on different ways of using the capture.

    • [ ] --- Empty or no variables defined. In this case using any external variables in the lambda is an error.

    • [param1, & param2] --- "param1" is captured by value, "param2" is captured by reference.

    • [&] --- In this case any external variable will be implicitly captured by reference.

    • [=] --- In this case any external variable is implicitly captured by value.

    • [&, param2] --- In this case "param2" is explicitly captured by value. And all other variables will be captured by reference

    • [=, & param] --- In this case "param1" is explicitly captured by reference. And all other variables will be captured by value

  • Uniform Intialization :   There were few gaps in C++03 related to the initializing type. C++11 provides the mechanism of Uniform Initialization which highlights the usage of consistent syntax to initialize the variables and objects. It introduces the concept of "Brace Initialization" in which the initializer values are enclosed within the braces "{ }".
    Ex. int var1[] {10, 12, 14, 16, 18};
          int var2{}, var3{50};
    Here "var1" is the dynamically allocated array having values 10, 12, 14, 16 and 18. "var2" is an uninitialized integer variable and "var3" is a initialized initrger variable with value "50".
    A simple example of uniform initialization using a code snippet:

                          
                            #include < iostream>
                            using namespace std;
    
                            class test_class
                            {
                              test_class(int var1, int var2)
                                    : value1{var1},
                                      value2{var2;}
                              {                              
                              }
                              private:
                                int value1;
                                int value2;
                            };
    
                            int main() {
                              test_class tc(5, 10);
                              return 0;
                            }
                          
                        

    Uniform initialization does not replace constructor syntax. If a class contains an initializer list constructor, then it will takes priority over other forms of construction.

  • Smart Pointers :   While dealing with the pointers sometimes because of our mistake we will run onto bigger problems like memory leakage and program crash at runtime. Let's consider a scenario, suppose we are having a function inside which we are having a local pointer variable pointing to the object of a class. Now when we call this function this local pointer variable is created and memory is allocated that in the heap memory. Now after that when the execution comes out of the function then, that local pointer variable will get destroyed but the allocated memory will be still there because we haven't explicitly called the "delete" operator at the end of the function which will free the allocated memory. So that allocated part of memory will now become an unused and inaccessible part of the memory. Now suppose if this function will be called many times or will be called inside a loop having a huge number of iterations then, at last, we will be out of memory or there is the memory leak causing or program to crash.
    C++11 provides the solution for this problem in the form of "Smart pointer". Smart pointer will manage the memory on its own and will take care of the deallocation of the memory. Whenever the pointer will go out of the scope of execution then it will deallocate the memory. We don't need to explictly call the "delete" for this everytime.
    The main idea behind the Smart Pointer is to take a class having a pointer, a destructor(not a default one) and overloaded operators " * " and "->". So in this logic destructor will be automatically called when an object will go out of the scope, the memory allocated in the heap will automatically get deleted or freed for use again.
    Below is the small example smart pointer usage:

                          
                            #include < iostream>
                            using namespace std;
    
                            class MyClass { 
                              int* val; // declearing pointer of type integer 
    
                              public: 
                                // Class Constructor
                                explicit MyClass(int* a = NULL) {
                                  val = a; 
                                } 
                            
                                // Destructor definition
                                ~SmartPtr() {
                                  delete (val); 
                                } 
                            
                                // overloading required pointer dereferencing operator 
                                int& operator*() { 
                                  return *val; 
                                } 
                          }; 
                            
                          int main() 
                          { 
                            MyClass c_ptr(new int()); 
                              *c_ptr = 5; 
                              cout << *c_ptr; 
                            
                              return 0; 
                          }
                          
                        

    The output will be:

                           
                            5
                           
                        

    Here we need not have to explicitly call delete at the end of the main function when the class object "c_ptr" goes out of scope. At this point, the destructor will be called inside which we had provided the logic for delete the pointer.
    There are different types of smart pointer:

    • Unique pointer:   In this concept, if one pointer( let say it "ptr1") is pointing to an object( let say it "obj") of the class then at the same time another pointer( let say it "ptr2") will not point to the same object. It means it not allowed to share the object with another pointer. We can only transfer the control of obj from ptr1 to ptr2.
      We can use the unique using the keyword "unique_ptr". It's syntax is :
      unique_ptr < data-type/class-name> Object-name (new Constructor-call)

    • Shared pointer:   In this type, one or more pointer can point to the class object at the same time. Also in doing so the reference counter will be maintained by using use_count() function.
      We can use the unique using the keyword "unique_ptr". It's syntax is :
      shared_ptr < data-type/class-name> Object-name (new Constructor-call)

    • Weak pointer:   It is similar to that of the shared pointer, the only difference is that, in this case, the reference count will not be maintained.

  • Range-based for loop :   In C++ now it is allowed to iterate over a range of elements in the for loop which was not available before that.
    Below is the example for the same:

                           
                            int list_val[5] = {10, 15, 20, 25, 30};
                            ....
                            .... 
                            for(int& var : list_val){
                              var = var*2;
                            }
                            ...
                           
                        

    Above is the style for writing the range based for loop. Here it will iterate over each element of the list and update the new values to it.

  • Multithreading :   With this version of C++, the support of multithreading programming was added. The previous memory model was also updated to execute more than one thread at a time and new library functions were added so that threads can communicate with each other.

  • Initializer list :   An Initializer list is a concept in which we pass a list of arguments in braces to a structure or an array. Also, the argument should be in the same order as the member's definitions of the structure. This will be very helpful while initializing the members of a structure with some values.
    In the previous version, it was allowed only for Structures. But in C++11 this feature is also extended to Classes and standard containers such as std::vector.
    So now we can easily initialize the data members of a class a the time when its object is created.

  • Null pointer constant :   C++11 introduced a new keyword "nullptr" to indicate as a distinguished null pointer constant. This is of type nullptr_t, which is implicitly convertible and comparable to any pointer type or pointer-to-member type.
    Its examples are:
      int* num = nullptr;
      char* val = nullptr;  etc.

C++ 14

In C++14 mainly the revision and extension of some features of the previous version were done. Below are some of the important updates that were made in this version.

  • Update in constexpr :   The idea of constexpr-declared function was first introduced in C+11. It is the function that could be executed at compile time. But there was some restriction while using them.
    In C++14 those restriction were removed. Now onwards we are having below freedom while using constexpr-declared function.

    • Now it contains the conditional branching statement like "if" and "switch".

    • Any looping statement is allowed now, including range-based for.

    • Added calls to any non-const constexpr-declared non-static member functions.

    • Except "static" or "therad-local" variables now all declarations are allowed. And also variable declarations without initializers are forbidden.

  • Variable templates :   Earlier only functions, classes, or type aliases were allowed to be templated. Now from C++14 onwards, users are allowed to create the variables which are templated. For example, if we are having a template of pi(value = 3.1415...) then using this template we can further create the variable "pi" for various types like for int(value equals to 3), float, or double(value equals to 3.141), etc. All the generic rules of the template will apply to these types of variable declarations and definitions.

  • Return type deduction :   Earlier the feature of return type deduction is available only for lambda functions. C++14 extends this feature to all functions. In order to dedeuce the return type, the function must be declared with "auto" as the return type.
    Below is the example for the same.

                          
                            auto test_function(int a){
                              a = a+10;
                              return a;
                            }                           
                          
                        

  • Shared mutexes and locking :   C++14 adds a shared timed mutex and a companion shared lock type.

  • Generic lambdas :  C++14 introduced the generalization of the lambda functions. Earlier the parameters of lambda functions need to be declared with concrete types. But C++14 allows it to declare the lambda functions paramaters with the "auto" type specifier.
    Ex.
    auto lambda_func = [](auto val1, auto val2) {return val1 + val2;};

  • Binary literals :   C++14 also provided the support to specify the numeric literals in binary form. The syntax uses the prefixes 0b or 0B.

C++ 17

In C++17, apart form the existing features update many new features were introduced. SOme important changes in C++17 can be summarized as below.

  • Inline variables were introduced which allows the definition of variables in header files. These follow the same rules to that of inline functions.

  • Initializers in "if" and "switch" statement were introduced.

  • Improvement made in the usage of "auto".

  • Fold Expressions were introduced.

  • Class template argument deduction (CTAD) was introduced which supports in constructor deduction.

  • Added the uniform container access like std::size, std::empty and std::data.

  • Introduced a new file system library based on boost::filesystem.

  • Added new mathematical special functions, including elliptic integrals and Bessel functions.

  • In Exception Handling, "std::uncaught_exception" is replaced by "std::uncaught_exception".

  • Added new library functions like, std::any, std::optional and std::string_view

  • Introduced the support for nested Namespace definitions.

C++ 20

C++20 is the revision of C++17 and the current version of C++. It was approved in September 2020 and published by ISO in December 2020. In this revision, some new features were introduced, some existing features were improved, and also some old features were removed and deprecated.
Some important features which were added and updated can be listed as below.

  • Revision in Memory model.

  • Support for three-way comparison using operator "<=>" known as "spaceship operator".

  • Allow using string literals as template parameters.

  • Using [=, this] as a lambda capture. Also, many other improvements were made in lambda usage.

  • Introduced "constinit" keyword that will help in declearing a variable with static or thread storage duration.

  • Expanded constexpr usage to virtual functions, union,try and catch statements, dynamic_cast and typeids, std::pointer_traits.

  • Introduced "consteval" keyword for declearing immediate functions.

  • Added "std::make_shared" and "std::allocate_shared" for arrays.

  • Added the support of creating smart pointer default initialization.

  • Support of "std::erase" and "std::erase_if" in standard containers.

  • Introduced "std::bit_cast<>" for type casting of object representations

  • Added atomic smart pointers (such as "std::atomic< shared_ptr < T>> "and "std::atomic< weak_ptr < T>>")

Also some of the removal of existing features were made. These removed and deprecated features can be listed as below.

  • Removal of C-derived headers "< ccomplex>", "< ciso646>", "< cstdalign>", "< cstdbool>" and "< ctgmath>".

  • Some of old libraries such as std::uncaught_exception, std::raw_storage_iterator, std::is_literal_type, std::is_literal_type_v, std::result_of and std::result_of_t were removed.

  • In Exception handling use of "throw()" function is removed.

  • Usage of most cases of the "volatile" keyword has been deprecated.

  • Using comma operator in subscript expressions has been deprecated.




For detailed C++ Study material follow the link:- C++ Learning Series