C++ 11 adds quite a lot features. Here is a brief introduction to the features of C++ 11.
Type inference
Automatically deduce the type of a variable using the initialization expression. This makes code concise, flexible and less redundant. There are two ways to do it.
auto
auto is used for defining variables.
e.g. auto a = 5; // type is int
auto i = students.begin(); // iterator
auto cannot be used for function parameters and member variables of a class or for declaring arrays.
const and volatile specifiers are removed while initializing variables from other variables, but they are honored in case of references
auto cannot be used for function parameters and member variables of a class or for declaring arrays.
const and volatile specifiers are removed while initializing variables from other variables, but they are honored in case of references
e.g. const vector<int> values;
auto a = values; // type of a is vector
auto &b = values; // type of b is const vector
decltype
decltype() finds out type of expression passed to it. It can be used as a type specifier in place of type name.
e.g.
auto b = 10; // b is of type int
decltype(b) a; // same as int a
int i = 10;
cout << typeid(decltype(i+1.0)).name() // prints "double"
decltype is mostly used to specify return type of a templated function where return type depends on the template arguments. This syntax of declaring functions is known as functions with trailing return types.
template< typename x, typename y>
auto add(X x, Y y) -> decltype(x+y)
{
return x+y;
}
auto a = add (1, 2); // a is type of int
lambda expression
lambda provides a way to define 'in-place' anonymous functions.
syntax:
[capture block] (parameter list) mutable exception_spec -> return type
{
lambda body
}
e.g.
for_each(v. begin(), v.end(), [] (Student &student) { cout << student.name() << endl;})
where --
[] - lambda introducer - tells the compiler that the expression is a lambda expression.
() - parameter list. just like normal function parameters. It cannot have default parameters.
{} - body of lambda. Its just like normal function body.
-> - trailing return type to specify return type of lambda expression.
Internally lambda's are implemented as classes with function call opeartor, so they are effectively function objects.
Behind the scenes above expression will turn into
Behind the scenes above expression will turn into
class lambda_0
{
public:
void operator() (const Student &student) const
{
cout << student.name() << endl;
};
};
for_each(v. begin(), v.end(), lambda_o());
This class is called as 'closure' class and lambda's invocation is replaced with invocation of closure type's function call operator.
stored lambda
lambda can be stored in a variable and that variable can be used to invoke the lambda
e.g. auto max = [](int i, int j) { return i > j ? i : j; }
max(10, 20) ; // returns 20.
lambdas stored using auto specifier cannot be used as member variable or function parameter because of limitation of auto specifier. In such cases use std::function template to store the lambda.
1 #include <functional>
2 #include <iostream>
3 using namespace std;
4
5 class LambdaStore
6 {
7 // lambda's which take two int args and return bool.
8 function <int(int,int)> _stored_lambda;
9 public:
10 int max(int a, int b)
11 {
12 return _stored_lambda(a, b) ;
13 }
14 void set_lambda(function <int(int,int)> lambda)
15 {
16 _stored_lambda = lambda;
17 }
18 };
19
20 int main(void)
21 {
22 LambdaStore ls;
23 ls.set_lambda([](int a, int b) -> decltype(a) { return a > b? a : b ;});
24 cout << ls.max(10, 20);
25 }
capturing variables
lambda can refer to variables that are outside the scope of lambda expression. Since lambda can be stored and used later, the lambda can exist even if the variables that it refers to goes out of scope. So to make such variables available to lambda, c++ forms a closure of such variables and makes it available to lambda whenever required. These variables are called as 'free variables'.
The variable that need to be captures can be specified in the lambda introducer i.e. [].
e.g. [factor](int i) { return i * factor;}
Here, variable 'factor' would be captured by value. Variables can be captured by reference as well, by prefixing the variable name with symbol &
e.g. [&factor](int i ) {return i*factor;}
Note that only variables that are actually used in lambda expression will be captured and not all that are specified in the capture block.
To capture everything by value, use = sign in capture block.
e.g. [=] { return (a>b) && (c > d) }
To capture everything by reference, use & sign in capture block.
e.g. [&] { return (a>b) && (c>d)}
To capture all the data members of a class, use this in the capture block.
e.g. [this] { return (a>b) && (c>d)}
use [] if no reference to external variables in lambda expression. This means lambda can access only local variables defined within lambda body.
C++ allows specifying default mode, followed by specific mode for individual variables
e.g. [=, &a] ==> capture all by value except a which is captured by reference
[&, a] ==> capture all by reference except a which is captured by value.
Mutable lambda
Lambda can be made stateful by adding keyword 'mutable' to the lambda definition.
e.g. [runningTotal] (int a, int b) mutable { runningTotal = runningTotal + a + b;}
Data member initialization
C++03 allowed initialization of only static data members in a class definition. C++11 now allows initialization of data members in class definition.
e.g
class A {
int a = 10;
string s = "city";
public:
A() {}
};
This is useful when there are several constructors as same initialization code does not get repeated.
Inheriting base class constructor in derived class
Base class constructor can be inherited in derived class by applying 'using' keyword on the constructor.
e.g. class Derived: public Base
{
using Base::Base
};
Delegating constructor
A constructor can call another constructor of same class in member initialization list.
e.g. class JetPlane{
string _manufacturer;
string _model;
public:
JetPlane():JetPlane(3, "unknown", "unknown")
private:
JetPlane(int engines, string model, string manufacturer): _model(model), _manufacturer(manufacturer)
{
configure_engines(engines);
}
};
The delegated constructor is executed first followed by the delegating constructor. If a delegated constructor is called in the initialization list then you cannot do anything else in the initialization list.
default method
In c++03, generation of default copy constructor and default constructor is stopped if a parameterized constructor is provided.
C++11 allows user to request for default implementation of constructor, copy constructor, assignment operator, destructor etc. to be generated in a class by using default keyword.
e.g.
class Student{
public:
Student() {};
virtual ~Student() = default; // C++ will provide implementation of destructor
protected:
Student(const Student& rhs) = default; // C++ will provide implementation of copy constructor
};
C++11 allows user to request for default implementation of constructor, copy constructor, assignment operator, destructor etc. to be generated in a class by using default keyword.
e.g.
class Student{
public:
Student() {};
virtual ~Student() = default; // C++ will provide implementation of destructor
protected:
Student(const Student& rhs) = default; // C++ will provide implementation of copy constructor
};
delete method
delete keyword is used to indicate that function is not implemented in the class, its uncallable and cannot be used in any way.
The difference between undeclared function and deleted function is, in case of undefined function the compiler will still do the lookup and try to match the function but in case of deleted function, it will stop the lookup and report error.
delete keyword can be used to
1. Disable compiler generated functions.
class Student{
public:
Student() {};
~Student() {};
protected:
Student(const Student& rhs) = delete; // c++ will NOT provide implementation of copy constructor
};
2. Delete can be applied to any function, not just compiler generated functions.
3. It can be applied to non-class methods as well like-
a. Disable some instantiation of a template
class Student{
public:
Student() {};
~Student() {};
protected:
Student(const Student& rhs) = delete; // c++ will NOT provide implementation of copy constructor
};
2. Delete can be applied to any function, not just compiler generated functions.
3. It can be applied to non-class methods as well like-
a. Disable some instantiation of a template
e.g. template
void serialize(const T&)
{}
void serialize(PasswordStore&) = delete;
b. Disable unwanted conversion or heap allocation
class Altimeter {
public:
Altimeter(double) {}
Altimeter(int) = delete;
void operator new(size_t) = delete;
};
override and final
override
This keyword can be applied to a virtual function defined in derived class to tell the compiler that the derived class must be overriding a function defined in the base class.
This helps in preventing errors caused by overriding the virtual function with different function signature than the base class version which ends up in adding a new function in the derived class.
final
This keyword can be applied to classes or methods. If applied to a class, the class cannot be inherited further. If applied to a method, method cannot be overridden in derived class.Range based for loop
range based for loop can be used with containers or anything that supports concept of array defined by begin() and end() functions.
e.g. vector<int> v;
for (auto element: v)
cout << element << endl;
Internally the for loop is converted into
for(auto iter = v.begin(), v_end = v.end(); iter != v_end ; iter++)
{
element = *iter;
//statements in for loop.. e.g. in this case cout statement.
}
Note that range based for loop can be used with c style arrays as well.
Initializer list
Always prefer {} initialization over alternatives unless you have a strong reason not to.
list initialization does not allow narrowing. For example: int cannot be converted to another int which cannot hold its value, int to char is not allowed.
int i = 1000
char c {i} // compiler error
Do not use auto with initializer list.
auto j {99}; // type would be initializer_list instead of int
Initializer list can be used to assign default values to uninitialized variables. For example 0 to in, 0.0 to double and nullptr to pointer
int k{} // k = 0
int *p {} // p = nullptr
It can set each member of large buffer to zero. For example: char buf[1024] {}
Internally C++ 11 uses std::initializer_list to do the initialization.
Before C++11, it was not possible to initialize the dynamically created array in the same statement or initialize an array class member variable in initialization list.
list initialization does not allow narrowing. For example: int cannot be converted to another int which cannot hold its value, int to char is not allowed.
int i = 1000
char c {i} // compiler error
Do not use auto with initializer list.
auto j {99}; // type would be initializer_list
Initializer list can be used to assign default values to uninitialized variables. For example 0 to in, 0.0 to double and nullptr to pointer
int k{} // k = 0
int *p {} // p = nullptr
It can set each member of large buffer to zero. For example: char buf[1024] {}
Internally C++ 11 uses std::initializer_list to do the initialization.
Before C++11, it was not possible to initialize the dynamically created array in the same statement or initialize an array class member variable in initialization list.
e.g. int *values = new int[3] {1, 2, 3};
class Hexagon {
int _points[6];
Hexagon(): _points {1,2,3,4,5,6} {}
};
vector v {1,2,3};
This is possible with the help of STL template initializer_list. Internally compiler converts the brace delimited list into an instance of initializer_list and passes it to the constructor.
User defined class can support uniform initialization format by adding a constructor that takes initializer_list as a parameter.
#include <stdio.h>
#include <vector>
#include <iostream>
using namespace std;
// Template class
template <typename T>
class MyVector
{
vector<T> m_vector;
int dummy = 2;
public:
MyVector()
{
dummy = dummy + 10;
}
// delegating constructor
// this constructor supports uniform initialization i.e. MyVector<int> v = {1,2,3,4,5}
MyVector(const initializer_list<T>& il): MyVector()
{
for (auto i: il)
m_vector.push_back(i);
}
// added begin and end to support range based for loop.
auto begin() -> decltype(m_vector.cbegin())
{
return m_vector.cbegin();
}
// return type of the function is deduced from the decltype expression.
auto end() -> decltype(m_vector.cend())
{
return m_vector.cend();
}
};
int main()
{
MyVector<int> v = {1,2,3,4,5};
for(auto i : v)
cout << i << endl;
}
nullptr
nullptr is a new keyword for NULL pointers which is aimed at replacing NULL and 0. It helps in avoiding ambiguity in statements.
e.g. bool ambiguous(int) { return false; }
bool ambiguous(int*) { return true; }
ambiguous(NULL) ; // returns false
ambiguous(nullptr); // returns true
Applying delete to nullptr has no effect. in c++11, default value for pointers is nullptr.
Move semantics
Move copy constructor and move assignment operator generally performs shallow copy of member variables and switch ownership to the new variable.lvalue and rvalue
To understand move semantics, its necessary to understand lvalue and rvalue type of expressions.p
lvalue
|
rvalue
|
Appears on the left or right side of assignment operator.
|
Appears on the right side of the assignment operator.
|
Has a name
|
Does not have a name
|
Can have address
|
Cannot have address
|
Persists beyond boundaries of single expression
|
Temporary values that exist only for the duration of expression
|
e.g. var, *ptr, arr[n], ++x
|
e.g. (a+b), string(“abc”)
|
In case of function call, the return value of the function tells if its lvalue or rvalue. If a reference is returned then its lvalue otherwise its rvalue..
e.g. vector
v[0] // lvalue as opertor[] returns a reference
v.size() // rvalue as size() returns value of type size_t.
rvalue references
symbol '&&' is used to denote rvalue reference.. It can be initialized with non-const rvalue. Temporary instance of any object is always an rvalue.move constructor and assignment operator
1 #include <stdio.h>2 #include <string>
3 #include <iostream>
4 #include <vector>
5
6 using namespace std;
7
8 class Sstring {
9 public:
10 string *_str;
11 Sstring (string str)
12 {
13 cout << " constructor is called : " << str << endl;
14 _str = new string(str);
15 }
16
17 Sstring(const Sstring& rhs)
18 {
19 cout << "Copy CC is called. rhs.str = " << *(rhs._str) << endl;
20 _str = new string(*(rhs._str));
21 }
22
23
24 Sstring& operator=(const Sstring& rhs)
25 {
26
27 cout << "Assignment operator is claled " << endl;
28 if (this == &rhs)
29 return *this;
30 else
31 {
32 this->_str = new string(*(rhs._str));
33 return *this;
34 }
35 }
36
37 // Move constructor
38 Sstring(Sstring&& rhs): _str(nullptr)
39 {
40 cout << "Move constrcutor called. rhs.str = " << *(rhs._str) << endl;
41 this->_str = rhs._str;
42 rhs._str = nullptr;
43 }
44
45 // Move asignment operator
46 Sstring& operator=(Sstring&& rhs)
47 {
48 cout << "Move Assignment operator is called." << endl;
49
50 if ( this == &rhs )
51 {
52 delete this->_str;
53 this->_str = rhs._str;
54 rhs._str = nullptr;
55 }
56
57 return *this;
58 }
move semantics are not restricted for constructor and assignment operator. They can be used with setter methods as well.
e.g.
void JetPlane::set_model(string&& model)
{
_model = std::move(model);
model.clear();
}
std::move() function tells the compiler to use move overload otherwise by default compiler will chose copy overload.
variadic template functions
C supports writing functions that takes variable number of arguments. e.g. the famous printf() function which can take any number of arguments. The arguments are resolved at run time and so does not provide any type safety.Variadic template functions provides a way to write functions that can take arbitrary number of parameters in type safe way and all argument handling logic is resolved at compile time only.
e.g.
template
T adder(T value)
{ return value; }
template
T adder(T first, Args... args)
{
return first + adder(args...);
}
sum = adder(1,2,3,4,5);
Note that the function adder can take any number of arguments of any type as long as it can apply + operator on them. This type checking is done at compile time only. so sum = adder(true, true, false); will throw compilation error as you cannot apply + operator on boolean.
Variadic template functions are written just like recursive function. You need a base case and a general case which recurses.
There is no performance penalty as sequence of function call is pre-generated at compile time only.
Note the syntax (...) followed by typename in declaration of function and args... in the actual function call. typename... should appear as last parameter in template. It is called as template parameter pack. args... is called function parameter pack. The function parameter pack gets expanded (i.e. replaced with actual parameters) during function call.
One of the most famous use of variadic template function is in perfect function forwarding.
templates
template aliases
e.g. template
struct alloc {};
template
using vec = vector
vec
Note that typename and class keywords can be used interchangeably.
extern template
A template declared in a .h file and that .h file is included in two .cpp files then two template would get instantiated i.e. one in each object file. This can be prevented by declaring the template as extern in one of the cpp file. This fastens the compilation and reduces the size of object files.Before extern template, two object files would have template definition but linker would ultimately pick one and discard the other.
Reference collapsing and template argument deduction rule
C++03 did not allow taking reference of reference, c++11 allows it by following reference collapsing rules as specified below. Simple rule is '&' always wins
A& A&& becomes A&
A& A& becomes A&
A&& A& becomes A&
A&& A&& becomes A&&
There is special template argument deduction rule for function templates that take an argument by rvalue reference.
e.g. template(typename T)
void foo(T&&)
1. If function foo() is called with lvalue of type T then T resolves to T&.
2. If function foo() is called with rvalue of type T then T resolves to T&&.
These concepts plays important role in perfect function forwarding
Perfect forwarding
forwarding means that one function passes i.e. forwards its parameters to another function. The second function should receive the same objects that the first function received. This rules out by-value parameters and by-pointer parameters, only by-reference parameters provides this ability.
Perfect forwarding means we don't just forward objects, we also forward their salient characteristics: their types, whether they are lvalue or rvalue, whether they are const or volatile. This is made possible using reference collapsing, template argument deduction rules and std::forward() template function.
e.g. template
void wrapper(T&& t)
{
func(std::forward(t));
}
whereas std::forward is defined as
template
S&& forward(typename& a)
{
return static_cast (a);
}
** wrapper is called on lvalue
by special template deduction rule, wrapper template argument t would resolve to t& so following functions would be generated
void wrapper(T& &&t)
{
func(forward (t)); //parameter T& && => T&.
}
T& && forward(remove_ref::type& a)
{
return static_cast(a));
}
By applying reference collapsing rules this becomes
void wrapper(T& t) and
T& forward(T& a)
so the argument that is passed as lvalue is passed as reference to the wrapper and actual function.
** wrapper is called on rvalue
By special template deduction rule, wrappers template arg T resolves to t so the function becomes
void wrapper(T&& t)
{
func(forward(t));
}
so the arguments are passed by rvalue.