C++ 11 is a major version replacing C++98/03, It was approved by International Organization for Standardization on 12 August 2011, C++ 11 comes up with a whole set of new features and improvements to the C++ language overcoming many shortcomings of C++, using these features is though optional but you may find them undoubtedly helpful, let’s see them one by one.
Auto Keyword
Before C++11 every variable had to be declared with a data type, C++ 11 introduces auto keyword, auto keyword automatically instructs the compiler to declare the data type based on the value assigned to the variable, so the programmer need not worry about declaring data types when declaring variables, he can simply declare a variable as auto along with the variable name and the compiler will take care internally.
Let’s see the example.
#include <bits/stdc++.h> using namespace std; int main() { auto a = 5; auto b = 5.4; auto ptr = &x; cout << typeid(a).name() << endl << typeid(b).name() << endl << typeid(ptr).name() << endl; return 0; }
Output:
int double int *
On printing the variable information using the typeid() operator we get int for variable a, double for variable b, and int * for variable ptr, so we can see that compiler automatically declares the data types. The rule for auto variable declaration is that the variable should be initialized at the time of declaration, if the variable is not initialized at the time of declaration or initialized later then the compiler throws an error. The mechanism of an auto keyword is also referred to as Type Inference, as all the types are deduced in the compiler phase only, the time for compilation increases slightly but it does not affect the run time of the program.
nullptr
nullptr keyword was specifically introduced in C++ 11 to overcome the shortcomings of the earlier NULL macro which is defined as (void *)0 , so the macro’s integral conversion is allowed and can be used with other data types as well which can create problems.
Let’s see an example.
#include<stdio.h> void func (int a){ cout<<a; } void func(char *s){ cout<<s; } void main(){ func(NULL); }
The above program will throw ambiguity error, ideally, the func(NULL) should call func(char *) but it does not since NULL is macro which can be converted to value 0 so the compiler cannot decide which function to call func(int a) also accepts NULL and func(char *s) also accepts NULL. To overcome such type of issues C++ 11 introduces nullptr keyword, in the above program if you replace NULL with nullptr then void func(char *s) will get called, nullptr is the keyword that can be used at all places where NULL is expected, nullptr is not implicitly convertible or comparable to integral types like the NULL macro.
#include<stdio.h> void main(){ int a = nullptr; }
the above program throws a compilation error since nullptr cannot be converted to the integer type.
shared_ptr
shared_ptr comes under the roof of smart pointers auto_ptr is deprecated in C++11 and unique_ptr was introduced, though unique_ptr has better security its only suitable when we want a single pointer to object, but shared_ptr provides reference counting mechanism which maintains the reference count of all the copies pointing to a resource, so the counter is incremented when a new pointer points to the resource and decremented when its destructor is called. The reference to the resource will not be deleted until the reference count is greater than zero, so we can use shared_ptr when we want to use assign one raw pointer to multiple owners.
Below is an example of shared_ptr.
#include<iostream> using namespace std; int main() { shared_ptr<int> p1(new int); cout << p1.use_count() << endl; { shared_ptr<int> p2(p1); cout << p1.use_count() << endl; } cout << p1.use_count() << endl; getchar(); return 0; }
Output:
1 2 1
We can see in the output of the above program value of p1 is printed initially as 1 when p2 is referred to p1, the reference count is incremented and p1’s value is printed as 2, once p2’s scope is over again when we print p1 it prints 1, so it clearly demonstrates the reference count mechanism of shared_ptr.
Strongly Typed Enums
Prior to C++11 old type, enums had some shortcomings as they were only integer values and therefore can be compared to other enum types or only integers also which does not seem to be correct, also we cannot have different with the same value.
enum Games {FOOTBALL, CHESS}; enum OutdoorGames {FOOTBALL, CRICKET};
The above code will throw a compilation error since both enums have Football as a common value, so the enum values do not have scope under their name. C++ 11 introduces strongly typed enums and the new definition will look like.
enum class Games {FOOTBALL, CHESS}; enum class OutdoorGames {FOOTBALL, CRICKET};
The class keyword tells that the enum types are different and cannot be compared with integers or other types of enums, also they are scoped so each enum value can be accessed using its class name.
Games games = Games::FOOTBALL; If (Games::CHESS == games) { }
We can define strongly typed enums with fixed sizes like below
#include<cstdint> enum class Games: std::int8_t {CRICKET = 1, CHESS = 2 };
The cstdint header has types such as std::int8_t, std::int16_t, std::int32_t, and std::int64_t and unsigned versions that begin with u: std::uint8_t as well.
static assert : static assert is a mechanism provided in C++ 11 which allows checking conditions during compile time, if the condition does not match the compiler will throw an error and then stop the compilation process.
static_assert( constant_expression, string_literal );
Earlier to static_assert if we had to throw an error during compile time we had to use the #error directive
#ifndef __unix__ #error "Only Unix is supported" #endif
The problem with #error is that it can support simpler conditions but for complex conditions, it fails, so static_assert is much better than the old #error.
#include <iostream> using namespace std; template <class T, int Size> class List { static_assert(Size > 10, "The size of list is too small "); T m_values[Size]; }; int main() { List<int, 11> list1; List<int, 9> list2; return 0; }
In the above program, the static assert will throw compile-time error for List<int ,9> because we have implemented assert condition as the size > 10. One of the other advantages of static_assert over #error is that static assert is evaluated after pre-processor statements so it can check the size of a data type with sizeof using static_assert.
Variadic Template
Earlier to C++11 we have seen templates with a fixed number of arguments, which had to be specified when the template was created, but C++ 11 provides a facility to pass a variable number of arguments to the function templates.
template <typename… Values> class collection;
The above template class collection will take any number of arguments passed to the template when its created, here we have created the template with four arguments
collection <char *, int, float, string> object_name;
We can pass zero arguments as well, so the collection <> object_name; will be created without any parameters. Variadic templates can also be applied to functions, which can provide type-safe facilities to variadic functions kind of printf which needs variable kind of arguments.
template<typename... params> void printf(const std::string &str_format, params... parameters);
Range-Based for Loops
C++ 11 introduces ranged-based loops that execute over a range, range-based loops are easy to write and readable as compared to the older for loops, the use of auto keyword makes it easier to use.
for ( range_declaration : range_expression ) loop_statement
range_declaration: should be a variable of range_expression type or a reference to it. Often auto variables are used for automatic type detection.
range_expression: any expression that represents a suitable sequence or a braced-init-list.
loop_statment: statements that need to be executed inside the loop.
Let’s see some of the examples of ranged based loops with different range_expressions:
#include <iostream> using namespace std; int main() { int array[5] = { 1,2,3,4,5 }; for (int i : array) { cout << i; } cout << endl; for (int i : { 1, 2, 3, 4, 5 }) { cout << i; } cout << endl; vector<int> v = { 1, 2, 3, 4, 5 }; for (auto i : v) { cout << i; } cout << endl; return 0; }
Output:
1 2 3 4 5 1 2 3 4 5 1 2 3 4 5
Initializer lists: Before C++ 11 there were certain limitations on initializations of member arrays, dynamically allocated arrays, and standard libraries containers.
class X { int arr[100]; X(); //no proper way to initialize the array };
In the same way, we cannot initialize dynamically allocated arrays.
int *buffer=new int[100];
Even we have to initialize vector string like below we don’t have any standard way to initialize them.
vector <string> ; vs.push_back("Cat”); vs.push_back("Tiger"); vs.push_back("Lion);
C++11 resolves the above issues by introducing a universal initialization notation that applies to every type like STL’s arrays, dynamically allocated arrays the initialization unit is called brace-init.
int x{0}; string s1{"hello"}; string s2{s1}; vector <string> str{"Cat", "Tiger", "Lion"}; map <string, string> stars { {"John, "123456”}, {"Chris", "789010}}; double *pd= new double [3] {0.5, 1.2, 12.99}; class A { int arr[4]; public: A(): arr{10,11,12,13} {} }; class A { int arr[5] {1,2,3,4}; public: A(); };
We can even initialize class array like above, and whenever the default constructor is called the array will get initialized.
noexcept
The noexcept keyword decides if the function should throw an exception or not, noexcept comes in two forms as a specifier and as an operator.
noexcept as specifier
void func1() noexcept; // does not throw void func2() noexcept(true); // does not throw void func3() throw(); // does not throw void func4() noexcept(false); // may throw
As you can see in the above examples the noexcept specification is equal to noexcept(true) specification and does not throw the exception, noexcept(false) means the function can throw exception, the throw is equivalent to noexcept(true) but it is deprecated in C++11, even though noexcept is used with functions it cannot be used for function overloading.
noexcept as operator
The noexcept operator performs a compile-time check that returns true if an expression is declared to not throw any exceptions. It can be generally used with function templates noexcept specifier to declare that the function will throw exceptions for some types but not others.
#include <iostream> using namespace std;
void func1(); void func2() noexcept; auto var1 = []{}; auto var2 = []() noexcept {}; int main() {
std::cout << std::boolalpha << "Is func1() noexcept? " << noexcept(func1()) << '\n' << "Is func2() noexcept? " << noexcept(func2()) << '\n' << "Is var1 noexcept?" << noexcept(var1()) << '\n' << "Is var2 noexcept?" << noexcept(var2()) << '\n' }
Output:
Is may_throw() noexcept? false Is no_throw() noexcept? true Is lmay_throw() noexcept? false Is lno_throw() noexcept? true
Move
std::move allows conversion of r-value reference to l-value reference without creating its copy, the move is a standard library, C++11 provides move constructor and assignment operator.
move constructor: Whenever a copy constructor is called a temporary object is created, which is definitely time-consuming and resulting in overheads, to overcome this we use the move constructor.
#include <iostream> #include <vector> using namespace std;
class X{ int *ptr; public: X(){ ptr = new int ; }
A( const A & obj){ this->ptr = new int; } ~A(){ delete ptr; } }; int main() { vector <X> vec; vec.push_back(X()); return 0; }
In the above code, a temporary object for class A is created when the copy constructor is called.
Let’s see how to implement the move constructor.
X ( X && obj){ this->ptr = obj.ptr; obj.ptr = NULL; }
As we see move constructor for class X uses && operator, now instead of calling the copy constructor, the move constructor will be called which does not create any temporary objects but does the copying.
move assignment operator: In C++ 11 move assignment operator = is used to transfer a temporary object to an existing object, the move assignment operator is different than the move constructor, move assignment operator can be called on an existing object, the move operator can be overloaded.
class myString { public: myString& operator=(myString && other) { if (this != &other) { delete[] this->str; // Free this string's original data. this->str = other.str; // Copy the other string's data pointer into this string. other.str = nullptr; // Finally, reset the other string's data pointer. } return *this; } private: char* str; };
Lambda
A lambda function is an inline function that can be passed to another function as a parameter just like function pointers, Let’s see a very basic syntax of the lambda function.
#include <iostream> using namespace std; int main() { auto func = [] () { cout << "Hello world"; }; func(); }
The lambda function starts from [], which instructs the compiler that we are starting a lambda function, () indicates parameters to be passed and then the function body, the return type is automatically decided by the compiler as we are using the auto keyword.
#include <algorithm> #include <cmath> #include <iostream> using namespace std; void mySort(int* x, unsigned n) { std::sort(x, x + n, [] (int inp1, int inp2) {return (inp1 < inp2); } ); }
In the above std::sort function, we are passing a lambda function as the 3rd parameter which decides sorting order ascending /descending.
Override
As we know that in function overriding the base class function is redefined with the same signature in the derived class, but in some cases, the programmer may make mistake in redefining the function in the derived class, so C++ 11 introduces override keyword to keep track if a base class defines a virtual function of exactly same signature and if the signature does not match, the compiler will throw an error.
#include <iostream> using namespace std; class Base { public: virtual void myFunc() { cout << "I am in base" << endl; } };
class derived : public Base { public: void myFunc(int a) { cout << "I am in derived class" << endl; } };
int main() { Base b; derived d; return 0; }
The above code gets compiled, but the user’s intention was to override myFunc from a base class, but due to a simple mistake of passing argument so the function overriding will not work here, so C++ 11 provides override keyword.
void myFunc(int a) override { cout << "I am in derived class" << endl; }
If we add an override keyword next to myFunc and compile the code we will get a compilation error.
Thread
C++ 11 introduces multithreading support which is defined in a thread header file and is used like std::thread, in earlier versions to achieve multithreading p threads or threads library was used, this library has portability issues on different operating systems so C++ 11 resolved it by adding multithreading support in the language itself.
Let’s see a complete program to understand the working of threads.
#include <iostream> #include <thread> using namespace std; void func(int cnt) { for (int i = 0; i < cnt; i++) { cout << "Thread using function pointer \n"; } } class myThread { public: void operator()(int cnt) { for (int i = 0; i < cnt; i++) cout << "Thread using function object \n"; } }; int main() { cout << "Threads 1 and 2 and operating independently" << endl; thread t1(func, 2); thread t2(myThread(), 2); auto lf = [](int cnt) { for (int i = 0; i < cnt; i++) cout << "Thread using lambda expression \n"; }; thread t3(lf, 2); t1.join(); t2.join(); t3.join(); return 0; }
The above example shows how the threads can be created and called using functions, objects, lambda expressions, also how to join the threads.