[20 Feb 2020] C++ 11 Study
[1] Lambda functions
C++14 generic lambdas make it possible to write:
auto lambda = [](auto x, auto y) {return x + y;};
On the other hand, C++11 requires that lambda parameters be declared with concrete types, e.g:
auto lambda = [](int x, int y) {return x + y;};
Furthermore, the new standard std::move function can be used to capture a variable in a lambda expression by moving the object instead of copying or referencing it:
std::unique_ptr ptr(new int(10));
auto lambda = [value = std::move(ptr)] {return *value;};
[2] Constexpr
A constexpr-declared function in C++11 is a function which can be executed at compile time to produce a value to be used where a constant expression is required, such as when instantiating a template with an integer argument. While C++11 constexpr functions could only contain a single expression, C++14 relaxes those restrictions by allowing conditional statements such as if and switch, and also allowing loops, including range-based for loops.
Type deduction
C++14 allows return type deduction for all functions, thus extending C++11 that only allows it for lambda functions:
auto DeducedReturnTypeFunction();
Since C++14 is a strongly-typed language, a few restrictions shall be taken into account:
If a function's implementation has multiple return statements, they must deduce the same type.
Return type deduction can be used in forward declarations, but the function definitions must be available to the translation unit that uses them before they can be used.
Return type deduction can be used in recursive functions, but the recursive call must be preceded by at least one return statement allowing to deduce the return type.
Another improvement to type deduction brought by C++14 is the decltype(auto) syntax, which allows to compute the type of a given expression using the same mechanism as auto. Both auto and decltype were already present in C++11, but they used different mechanisms to deduce types that could end up in producing different results.
[3] std::make_shared<T>
How not to assign pointer to shared_ptr,
//Compile Error
std::shared_ptr<int> p1 = new int(); // Compile error
Because shared_ptr constructor taking an argument is Explicit and in above line we are calling it implicitly. Best way to create a new shared_ptr object is using std::make_shared,
std::shared_ptr<int> p1 = std::make_shared<int>();
std::make_shared makes one memory allocation for both the object and data structure required for reference counting i.e. new operator will called only once.
[4] Detaching the associated Raw Pointer
To make shared_ptr object de-attach its attached pointer call reset() method i.e.
reset() function with no parameter:
p1.reset();
It decrease its reference count by 1 and if reference count becomes 0 then it deleted the pointer
reset() function with parameter:
p1.reset(new int(34));
In this case it will point to new Pointer internally, hence its reference count will again become 1.
Resetting Using nullptr:
p1 = nullptr;
set the shared_ptr object with nullptr.
[5] shared_ptr is a psuedo pointer
shared_ptr acts as normal pointer i.e. we can use * and -> with shared_ptr object and can also compare it like other shared_ptr objects;
Complete example is as follows,
#include <memory> // We need to include this for shared_ptr
int main()
{
// Creating a shared_ptr through make_shared
std::shared_ptr<int> p1 = std::make_shared<int>();
*p1 = 78;
std::cout << "p1 = " << *p1 << std::endl;
// Shows the reference count
std::cout << "p1 Reference count = " << p1.use_count() << std::endl;
// Second shared_ptr object will also point to same pointer internally
// It will make the reference count to 2.
std::shared_ptr<int> p2(p1);
// Shows the reference count
std::cout << "p2 Reference count = " << p2.use_count() << std::endl;
std::cout << "p1 Reference count = " << p1.use_count() << std::endl;
// Comparing smart pointers
if (p1 == p2)
{
std::cout << "p1 and p2 are pointing to same pointer\n";
}
std::cout<<"Reset p1 "<<std::endl;
p1.reset();
// Reset the shared_ptr, in this case it will not point to any Pointer internally
// hence its reference count will become 0.
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
// Reset the shared_ptr, in this case it will point to a new Pointer internally
// hence its reference count will become 1.
p1.reset(new int(11));
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
// Assigning nullptr will de-attach the associated pointer and make it to point null
p1 = nullptr;
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
if (!p1)
{
std::cout << "p1 is NULL" << std::endl;
}
return 0;
}
Output:-
p1 = 78
p1 Reference count = 1
p2 Reference count = 2
p1 Reference count = 2
p1 and p2 are pointing to same pointer
Reset p1
p1 Reference Count = 0
p1 Reference Count = 1
p1 Reference Count = 0
p1 is NULL
[6] what is std::unique_ptr ?
unique_ptr<> is one of the Smart pointer implementation provided by c++11 to prevent memory leaks. A unique_ptr object wraps around a raw pointer and its responsible for its lifetime. When this object is destructed then in its destructor it deletes the associated raw pointer.
unique_ptr has its -> and * operator overloaded, so it can be used similar to normal pointer.
Checkout the following example,
#include <iostream>
#include <memory>
struct Task
{
int mId;
Task(int id ) :mId(id)
{
std::cout<<"Task::Constructor"<<std::endl;
}
~Task()
{
std::cout<<"Task::Destructor"<<std::endl;
}
};
int main()
{
// Create a unique_ptr object through raw pointer
std::unique_ptr<Task> taskPtr(new Task(23));
//Access the element through unique_ptr
int id = taskPtr->mId;
std::cout<<id<<std::endl;
return 0;
}
Output:- 3
Task::Constructor
23
Task::Destructor
unique_ptr<Task> object taskPtr accepts a raw pointer as arguments. Now when function will exit, this object will go out of scope and its destructor will be called. In its destructor unique_ptr object taskPtr deletes the associated raw pointer.
So, even if function is exited normally or abnormally (due to some exception), destructor of taskPtr will always be called. Hence, raw pointer will always get deleted and prevent the memory leak.
[7] Unique Ownership of unique pointer
A unique_ptr object is always the unique owner of associated raw pointer. We can not copy a unique_ptr object, its only movable.
As each unique_ptr object is sole owner of a raw pointer, therefore in its destructor it directly deletes the associated pointer. There is no need of any reference counting, therefore its very light.
Creating a empty unique_ptr object
Let’s create a empty unique_ptr<int> object i.e.
// Empty unique_ptr object
std::unique_ptr<int> ptr1;
ptr1 has no raw pointer associated with it. Hence its empty.
[8]Check if a unique_ptr<> object is empty
[10]Reseting a unique_ptr
Calling reset() function on a unique_ptr<> object will reset it i.e. it will delete the associated raw pointer and make unique_ptr<> object empty i.e.
// Create a unique_ptr object through raw pointer
std::unique_ptr<Task> taskPtr(new Task(23));
// Reseting the unique_ptr will delete the associated
// raw pointer and make unique_ptr object empty
taskPtr.reset();
[11] unique_ptr object is not copyable
As unique_ptr<> is not copyable, only movable. Hence we can not create copy of a unique_ptr object either through copy constructor or assignment operator.
// Create a unique_ptr object through raw pointer
std::unique_ptr<Task> taskPtr2(new Task(55));
// Compile Error : unique_ptr object is Not copyable
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error
// Compile Error : unique_ptr object is Not copyable
taskPtr = taskPtr2; //compile error
Both copy constructor and assignment operator are deleted in unique_ptr<> class.
[12]Transfering the ownership of unique_ptr object
C++14 generic lambdas make it possible to write:
auto lambda = [](auto x, auto y) {return x + y;};
On the other hand, C++11 requires that lambda parameters be declared with concrete types, e.g:
auto lambda = [](int x, int y) {return x + y;};
Furthermore, the new standard std::move function can be used to capture a variable in a lambda expression by moving the object instead of copying or referencing it:
std::unique_ptr ptr(new int(10));
auto lambda = [value = std::move(ptr)] {return *value;};
[2] Constexpr
A constexpr-declared function in C++11 is a function which can be executed at compile time to produce a value to be used where a constant expression is required, such as when instantiating a template with an integer argument. While C++11 constexpr functions could only contain a single expression, C++14 relaxes those restrictions by allowing conditional statements such as if and switch, and also allowing loops, including range-based for loops.
Type deduction
C++14 allows return type deduction for all functions, thus extending C++11 that only allows it for lambda functions:
auto DeducedReturnTypeFunction();
Since C++14 is a strongly-typed language, a few restrictions shall be taken into account:
If a function's implementation has multiple return statements, they must deduce the same type.
Return type deduction can be used in forward declarations, but the function definitions must be available to the translation unit that uses them before they can be used.
Return type deduction can be used in recursive functions, but the recursive call must be preceded by at least one return statement allowing to deduce the return type.
Another improvement to type deduction brought by C++14 is the decltype(auto) syntax, which allows to compute the type of a given expression using the same mechanism as auto. Both auto and decltype were already present in C++11, but they used different mechanisms to deduce types that could end up in producing different results.
[3] std::make_shared<T>
How not to assign pointer to shared_ptr,
//Compile Error
std::shared_ptr<int> p1 = new int(); // Compile error
Because shared_ptr constructor taking an argument is Explicit and in above line we are calling it implicitly. Best way to create a new shared_ptr object is using std::make_shared,
std::shared_ptr<int> p1 = std::make_shared<int>();
std::make_shared makes one memory allocation for both the object and data structure required for reference counting i.e. new operator will called only once.
[4] Detaching the associated Raw Pointer
To make shared_ptr object de-attach its attached pointer call reset() method i.e.
reset() function with no parameter:
p1.reset();
It decrease its reference count by 1 and if reference count becomes 0 then it deleted the pointer
reset() function with parameter:
p1.reset(new int(34));
In this case it will point to new Pointer internally, hence its reference count will again become 1.
Resetting Using nullptr:
p1 = nullptr;
set the shared_ptr object with nullptr.
[5] shared_ptr is a psuedo pointer
shared_ptr acts as normal pointer i.e. we can use * and -> with shared_ptr object and can also compare it like other shared_ptr objects;
Complete example is as follows,
#include <memory> // We need to include this for shared_ptr
int main()
{
// Creating a shared_ptr through make_shared
std::shared_ptr<int> p1 = std::make_shared<int>();
*p1 = 78;
std::cout << "p1 = " << *p1 << std::endl;
// Shows the reference count
std::cout << "p1 Reference count = " << p1.use_count() << std::endl;
// Second shared_ptr object will also point to same pointer internally
// It will make the reference count to 2.
std::shared_ptr<int> p2(p1);
// Shows the reference count
std::cout << "p2 Reference count = " << p2.use_count() << std::endl;
std::cout << "p1 Reference count = " << p1.use_count() << std::endl;
// Comparing smart pointers
if (p1 == p2)
{
std::cout << "p1 and p2 are pointing to same pointer\n";
}
std::cout<<"Reset p1 "<<std::endl;
p1.reset();
// Reset the shared_ptr, in this case it will not point to any Pointer internally
// hence its reference count will become 0.
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
// Reset the shared_ptr, in this case it will point to a new Pointer internally
// hence its reference count will become 1.
p1.reset(new int(11));
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
// Assigning nullptr will de-attach the associated pointer and make it to point null
p1 = nullptr;
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
if (!p1)
{
std::cout << "p1 is NULL" << std::endl;
}
return 0;
}
Output:-
p1 = 78
p1 Reference count = 1
p2 Reference count = 2
p1 Reference count = 2
p1 and p2 are pointing to same pointer
Reset p1
p1 Reference Count = 0
p1 Reference Count = 1
p1 Reference Count = 0
p1 is NULL
[6] what is std::unique_ptr ?
unique_ptr<> is one of the Smart pointer implementation provided by c++11 to prevent memory leaks. A unique_ptr object wraps around a raw pointer and its responsible for its lifetime. When this object is destructed then in its destructor it deletes the associated raw pointer.
unique_ptr has its -> and * operator overloaded, so it can be used similar to normal pointer.
Checkout the following example,
#include <iostream>
#include <memory>
struct Task
{
int mId;
Task(int id ) :mId(id)
{
std::cout<<"Task::Constructor"<<std::endl;
}
~Task()
{
std::cout<<"Task::Destructor"<<std::endl;
}
};
int main()
{
// Create a unique_ptr object through raw pointer
std::unique_ptr<Task> taskPtr(new Task(23));
//Access the element through unique_ptr
int id = taskPtr->mId;
std::cout<<id<<std::endl;
return 0;
}
Output:- 3
Task::Constructor
23
Task::Destructor
unique_ptr<Task> object taskPtr accepts a raw pointer as arguments. Now when function will exit, this object will go out of scope and its destructor will be called. In its destructor unique_ptr object taskPtr deletes the associated raw pointer.
So, even if function is exited normally or abnormally (due to some exception), destructor of taskPtr will always be called. Hence, raw pointer will always get deleted and prevent the memory leak.
[7] Unique Ownership of unique pointer
A unique_ptr object is always the unique owner of associated raw pointer. We can not copy a unique_ptr object, its only movable.
As each unique_ptr object is sole owner of a raw pointer, therefore in its destructor it directly deletes the associated pointer. There is no need of any reference counting, therefore its very light.
Creating a empty unique_ptr object
Let’s create a empty unique_ptr<int> object i.e.
// Empty unique_ptr object
std::unique_ptr<int> ptr1;
ptr1 has no raw pointer associated with it. Hence its empty.
[8]Check if a unique_ptr<> object is empty
There
are two ways to check if a unique_ptr<>
object is empty
or it has a raw pointer associated with it i.e.
Method
1 :
//
Check if unique pointer object is empty
if(!ptr1)
std::cout<<"ptr1
is empty"<<std::endl;
Method
2:
//
Check if unique pointer object is empty
if(ptr1
== nullptr)
std::cout<<"ptr1
is empty"<<std::endl;
[9] Creating a unique_ptr object with raw pointer
To create a unique_ptr<> object that is non empty, we need to pass the raw pointer in its constructor while creating the object i.e.
// Create a unique_ptr object through raw pointer
std::unique_ptr<Task> taskPtr(new Task(23));
We can not create a unique_ptr<> object through assignment, otherwise it will cause compile error
// std::unique_ptr<Task> taskPtr2 = new Task(); // Compile Error
[10]Reseting a unique_ptr
Calling reset() function on a unique_ptr<> object will reset it i.e. it will delete the associated raw pointer and make unique_ptr<> object empty i.e.
// Create a unique_ptr object through raw pointer
std::unique_ptr<Task> taskPtr(new Task(23));
// Reseting the unique_ptr will delete the associated
// raw pointer and make unique_ptr object empty
taskPtr.reset();
[11] unique_ptr object is not copyable
As unique_ptr<> is not copyable, only movable. Hence we can not create copy of a unique_ptr object either through copy constructor or assignment operator.
// Create a unique_ptr object through raw pointer
std::unique_ptr<Task> taskPtr2(new Task(55));
// Compile Error : unique_ptr object is Not copyable
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error
// Compile Error : unique_ptr object is Not copyable
taskPtr = taskPtr2; //compile error
Both copy constructor and assignment operator are deleted in unique_ptr<> class.
[12]Transfering the ownership of unique_ptr object
We
cannot copy a unique_ptr
object, but we can move them. It means a unique_ptr
object can transfer the owner ship of associated raw pointer to another unique_ptr
object. Let’s understand by an example,
Create
a unique_ptr
Object i.e.
//
Create a unique_ptr
object through raw pointer
std::unique_ptr<Task>
taskPtr2(new
Task(55));
taskPtr2
is not empty.
Now
transfer the ownership of associated pointer of Task to a new unique_ptr
object i.e.
{
//
Transfer the ownership
std::unique_ptr<Task>
taskPtr4 = std::move(taskPtr2);
if(taskPtr2
== nullptr)
std::cout<<"taskPtr2
is empty"<<std::endl;
//
ownership of taskPtr2 is transfered
to taskPtr4
if(taskPtr4
!= nullptr)
std::cout<<"taskPtr4
is not empty"<<std::endl;
std::cout<<taskPtr4->mId<<std::endl;
//taskPtr4
goes out of scope and deletes the associated raw pointer
}
std::move()
will convert the taskPtr2 to a RValue
Reference. So that move constructor of unique_ptr
is invoked and associated raw pointer can be transferred to taskPtr4.
taskPtr2
will be empty after transferring the ownership of its raw pointer to taskPtr4.
[13]Releasing the associated raw pointer
[13]Releasing the associated raw pointer
Calling
release() on unique_ptr
object will release the ownership of associated raw pointer from the
object.
It returns the raw pointer.
It returns the raw pointer.
//
Create a unique_ptr
object through raw pointer
std::unique_ptr<Task>
taskPtr5(new
Task(55));
if(taskPtr5
!= nullptr)
std::cout<<"taskPtr5
is not empty"<<std::endl;
//
Release the ownership of object from raw pointer
Task
* ptr
= taskPtr5.release();
if(taskPtr5
== nullptr)
std::cout<<"taskPtr5
is empty"<<std::endl;
[14]Checkout
complete example as follows,
#include
<iostream>
#include
<memory>
struct
Task
{
int
mId;
Task(int
id ) :mId(id)
{
std::cout<<"Task::Constructor"<<std::endl;
}
~Task()
{
std::cout<<"Task::Destructor"<<std::endl;
}
};
int
main()
{
//
Empty unique_ptr
object
std::unique_ptr<int>
ptr1;
//
Check if unique pointer object is empty
if(!ptr1)
std::cout<<"ptr1
is empty"<<std::endl;
//
Check if unique pointer object is empty
if(ptr1
== nullptr)
std::cout<<"ptr1
is empty"<<std::endl;
//
can not create unique_ptr
object by initializing through assignment
//
std::unique_ptr<Task>
taskPtr2 = new Task(); // Compile Error
//
Create a unique_ptr
object through raw pointer
std::unique_ptr<Task>
taskPtr(new
Task(23));
//
Check if taskPtr
is empty or it has an associated raw pointer
if(taskPtr
!= nullptr)
std::cout<<"taskPtr
is not empty"<<std::endl;
//Access
the element through unique_ptr
std::cout<<taskPtr->mId<<std::endl;
std::cout<<"Reset
the taskPtr"<<std::endl;
//
Reseting
the unique_ptr
will delete the associated
//
raw pointer and make unique_ptr
object empty
taskPtr.reset();
//
Check if taskPtr
is empty or it has an associated raw pointer
if(taskPtr
== nullptr)
std::cout<<"taskPtr
is empty"<<std::endl;
//
Create a unique_ptr
object through raw pointer
std::unique_ptr<Task>
taskPtr2(new
Task(55));
if(taskPtr2
!= nullptr)
std::cout<<"taskPtr2
is not empty"<<std::endl;
//
unique_ptr
object is Not copyable
//taskPtr
= taskPtr2; //compile error
//
unique_ptr
object is Not copyable
//std::unique_ptr<Task>
taskPtr3 = taskPtr2;
{
//
Transfer the ownership
std::unique_ptr<Task>
taskPtr4 = std::move(taskPtr2);
if(taskPtr2
== nullptr)
std::cout<<"taskPtr2
is empty"<<std::endl;
//
ownership of taskPtr2 is transfered
to taskPtr4
if(taskPtr4
!= nullptr)
std::cout<<"taskPtr4
is not empty"<<std::endl;
std::cout<<taskPtr4->mId<<std::endl;
//taskPtr4
goes out of scope and deletes the assocaited
raw pointer
}
//
Create a unique_ptr
object through raw pointer
std::unique_ptr<Task>
taskPtr5(new
Task(55));
if(taskPtr5
!= nullptr)
std::cout<<"taskPtr5
is not empty"<<std::endl;
//
Release the ownership of object from raw pointer
Task
* ptr
= taskPtr5.release();
if(taskPtr5
== nullptr)
std::cout<<"taskPtr5
is empty"<<std::endl;
std::cout<<ptr->mId<<std::endl;
delete
ptr;
return
0;
}
Output:-
ptr1
is empty
ptr1
is empty
Task::Constructor
taskPtr
is not empty
23
Reset
the taskPtr
Task::Destructor
taskPtr
is empty
Task::Constructor
taskPtr2
is not empty
taskPtr2
is empty
taskPtr4
is not empty
55
Task::Destructor
Task::Constructor
taskPtr5
is not empty
taskPtr5
is empty
55
Task::Destructor
[15]A
weak_ptr
is created as a copy of shared_ptr.
It provides access to an object that is owned by one or more shared_ptr
instances,
but
does not participate in reference counting. The existence or destruction of weak_ptr
has no effect on the shared_ptr
or its other copies. It is required in some cases to break circular references
between shared_ptr
instances.
Cyclic
Dependency (Problems with shared_ptr): Let’s
consider a scenario where we have two classes A and B, both have pointers to
other classes. So, it’s always be like A is pointing to B and B is pointing to
A. Hence, use_count
will never reach zero and they never get deleted.
This
is the reason we use weak
pointers(weak_ptr)
as they are not reference counted. So, the class in which weak_ptr
is declared doesn’t have strong hold of it i.e. the ownership isn’t shared, but
they can have access to these objects.
So,
in case of shared_ptr
because of cyclic dependency use_count
never reaches zero which is prevented using weak_ptr,
which removes this problem by declaring A_ptr
as weak_ptr,
thus class A does not own it, only have access to it and we also need to check
the validity of object as it may go out of scope. In general, it is a design
issue.
When
to use weak_ptr?
When you do want to refer to your object from multiple places – for those references for which it’s ok to ignore and deallocate (so they’ll just note the object is gone when you try to dereference).
When you do want to refer to your object from multiple places – for those references for which it’s ok to ignore and deallocate (so they’ll just note the object is gone when you try to dereference).
[16] what
is std::tuple
and why do we need it ?
std::tuple
is a type that can bind fixed size heterogeneous values together. We need
to specify the type of elements as template parameter while creating tuple
object.
Creating
a std::tuple
object
Let’s
declare a std::tuple
that is a collection of an int,
double and std::string
i.e.
//
Creating a tuple of int,
double and string
std::tuple<int,
double,
std::string>
result(7, 9.8, "text");
Now,
all the three types of variables are encapsulated in a single object. We can
return this tuple object from a function too. So, basically it helps us to
return multiple values from a function. Eventually, it helps us to avoid
creating unnecessary structs.
Header
file required, #include <tuple> // Required for std::tuple
[17] Getting
elements from a std::tuple
We
can get the element hidden in tuple object using std::get
function by specifying the index value as template argument.
Let’s
get the first element from tuple object i.e.
CODE//
Get First int
value from tuple
int
iVal
= std::get<0>(result);
Similarly
get the 2nd and 3rd element from tuple object i.e.
//
Get second double value from tuple
double
dVal
= std::get<1>(result);
//
Get third string value from tuple
std::string
strVal
= std::get<2>(result);
[18]
Comments
Post a Comment