[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
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
Calling release() on unique_ptr object will release the ownership of associated raw pointer from the object.
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).

[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

Popular posts from this blog

Kth most frequent Character in a given String

[16 Feb 2020] Given an array where every element occurs three times, except one element which occurs only once.

[17 Feb 2020] Given an array of integers, find the nearest smaller number for every element such that the smaller element is on left side.