Saturday, January 11, 2014

Chapter:15 Smart Pointers

Smart Pointer concept is not something which is newly introduced in C++11. However it has changed siginificantly from pervious standard,so I thought to cover this topic as well. If we read about on smart pointer on Wikipedia, we get the following:
"In computer science, a smart pointer is an abstract data type that simulates a pointer while providing additional features, such as automatic memory management or bounds checking. These additional features are intended to reduce bugs caused by the misuse of pointers while retaining efficiency. Smart pointers typically keep track of the  memory they point to."


In near future, it would became even more popular becasue its provides the garbage collectior like feature in C++. In addition to this, it is very important tool to write 'RAII(resource acquisition is initialization)' based programming. RAII is programming idom which is invented by Bjarne Stroustrup. This is very important idom as it always ensures that there would be call of destructors for all objects declared in that scope where exception has occured. This avoids any resource leak scenario under any cirumstances.


Above two points make smart pointer very useful tool. So we should try to avoid the use of raw pointer as much as possible and start using the smart pointer. Of course this would not be easy as there would be many other code which had been written based on raw pointer and sometime it is not possible(naturally) to express something using these tools. Current C++11 standard supports three type of smart pointer:


1. unique ptr.
2. shared ptr.
3. weak ptr.

The following sections describe how we can use these tools in our program.


The current program demonstrate the usage of 'unique ptr' in our program. Here I have written 'myvector' class which has 'std::vector' as member. I have also written two method of this class which simply delegate the work to 'std::vector'. The main intention behind writing this class was, I wanted to write constructor and destructor so that I can put the appropriate  message in these functions. This would help us to understand the object lifecycle in better way compared to 'std::vector".


In this program, first of all I have created an object(pmyvec) of this class  on free store. After that I have inserted two elements in 'myvector' object.
Now I have tried to fetch the value from the object 'myvector' for the given index. We can see that this is not correct as we are passing size of this object
in the index(it should be size -1), due to which it would throw the 'out-of-range' exception.


I did write this line deliberately as I wanted to explain that how RAII would  take care to call destructor of this class whenever exception occurs. And if everything goes fine, then at the end of the scope, 'myvector' object destrutor would get called without calling the 'delete' which we do with raw pointer. So in short it take care for both the scenario in very good manner and it always ensures that destructor gets called so that there is no resource leak in your program. By the way the above code is not so unusual in the real world program. Such mistakes in the program do exists in the code.


Now we verified that destructor gets called under the exception scenario so we should also see what happens in the normal case when everything works fine. We can comment out the source line which is causing the exception and run the program. It works as  expected and whenever 'myvector' object go out of scope, destructors gets called  without explicit 'delete' call.


If we have used the normal raw pointer instead of smart pointer, there can not be any automatic destructor call under the exception. We  have to write the code explicitly in each 'catch()' block to achieve that. And to handle the normal scenario we would have to call 'delete' at the end of the function. We can see that things are  becoming bit complicated and lot of repetitive code needs to be written to handle both  scenario even in such simple case. This is not so good and chances of mistake are  high so ideally we should avoid such style of code. Try by yourself and check the  behavior and feel the difference between raw pointer and smart pointer.



//Code Begins

#include<memory>
#include<vector>
#include<iostream>
#include<exception>

template<typename T>
class myvector {
public:
    myvector() {std::cout<<"myvector::myvector\n";}
    ~myvector(){std::cout<<"myvector::~myvector\n";}
    void insert_element(T val) {
        vec.push_back(val);
    }
    size_t size() {
        return vec.size();
    }
    T getat(size_t index) {
        return (vec.at(index));
    }
private:
    std::vector<T> vec;
};


void learn_unique_ptr(void) {
    try{
        std::unique_ptr<myvector<int>> pmyvec {new myvector<int>};
        /*do not bother about the deleting the pointer */
        pmyvec->insert_element(int{5});
        pmyvec->insert_element(int{20});
        auto sz = pmyvec->size();
        /*Comment out the below line so see the behavior under normal*/
        auto val = pmyvec->getat(sz);
        auto x = pmyvec.get();
        auto del = pmyvec.get_deleter();
        std::cout<<sz<<"\n";
    }

    catch(std::exception& e) {
        std::cout<<e.what()<<", Exception"<<std::endl;
    }
    catch(...) {
        std::cout<<"Unhandled Exception"<<std::endl;
    }
}


int main(int argc, const char* argv[]) {
    learn_unique_ptr();
    return 0;
}

//Code Ends



When I run the above program, I get the following output which is expected. Under both scenario, destructor has executed.


//Console Output Begins

$ ./test (Under exception scenario)
myvector::myvector
myvector::~myvector
vector::_M_range_check, Exception

$ ./test (Under Normal scenario)
myvector::myvector
2
myvector::~myvector

//Console Output Ends



In this section, we will learn how smart pointer (unique ptr) helps to return the pointer safely from a function. We need not bother about any component about releasing the memory once is is done with the usage. We can return the pointer from one function to other and the other function would keep on using that  particular pointer. Once the last function which was using the smart pointer  is about to go out of scope, destructor would get called automatically.


In this program, one function returns the smart pointer object to its caller. Now caller would use the smart pointer object in normal fashion. Once Caller function is about to finish,destructor would get called as there are no more active user for this object.


This is great and its makes program more robust and less error prone. It also does automatic resource management even if smart pointer objects has been returned from  one scope to other. Now user of the returned smart pointer objects do not need to worry about the 'delete' this memory. System would do automatically for us.


So we should use the 'unique ptr' in these scenarios. Its performance is comparable to raw pointer usage. Also it removes lot of burden from the programmer and makes system more safe.



//Code Begins

#include<memory>
#include<vector>
#include<iostream>
#include<exception>

struct x {
public:
    x(){
        std::cout<<"x::x()"<<std::endl;
    }
    ~x() {
        delete[] length;
        delete[] width;
        std::cout<<"x::~x()"<<std::endl;
    }

    std::size_t size{5};
    int* length{new int[size]};
    int* width{new int[size]};
};


std::unique_ptr<x> learn_unique_ptr(void) {
    std::unique_ptr<x> ux{new x{}};
    return ux;
}

int main(int argc, const char* argv[]) {
    std::unique_ptr<x> uy{};
    uy = learn_unique_ptr();
    auto val = uy->size;
    std::cout<<"Size Memeber Of:"<<val<<std::endl;
    return 0;
}

//Code Ends



When I run the above program, I get the following output.


//Console Output Begins

$ ./test
x::x()
Size Memeber Of:5
x::~x()

//Console Output Ends



In this section, we would learn how we can use the 'shared ptr' smart pointer. As the name suggests that, it should be used when we want to share the objects between two components and there is no explicit owner who should delete that  particular resource.


'shared ptr' manages \textbf{reference count} for a particular object. Its keep on  incrementing the count value whenever its shared/passed to the other component. Whenever its count value become 0 which indicate that there is no user of this object, it would call destructor. This is the basic philosophy of 'shared ptr'. We can see that it does more work and its complicated than 'unique ptr' pointers. Hence there would be significant overhead associated with the 'shared ptr'. So we should use it when that particular scenario cannot be handled using the  simpler 'unique ptr'.


In the current program, I have created a 'shared ptr' object(sx) and then passed by value to the two different threads. Inside the thread function, I have used the 'sx' object in normal fashion. Once both threads finish their execution, parent code would move ahead. Once it is about to come out from the current function scope,  its count value would become 0. At this point system would call destructor for this object.


As we can see that with 'shared ptr' we can safely pass smart pointer object to  different thread and component. System would take care to increase the reference count of this object. Once everyone is done with the usage of this object, reference count would become 0. At this point system has called the destructor of this class.


The rest of the program is self-explanatory  and it has been written for completeness. 'shared ptr' is great tool and it take care very difficult scenarios for us. Hope this make sense to reader.




//Code Begins

#include<memory>
#include<iostream>
#include<thread>
#include<chrono>
using namespace std::chrono;

class x {
public:
    x() {std::cout<<"x::x()"<<std::endl;}
    ~x() {std::cout<<"x::~x()"<<std::endl;}
    void display(void) {
        std::cout<<msg<<std::endl;
    }
private:
    std::string msg{"Sachin Is SuperMan"};
};

void thread_one(std::shared_ptr<x> sptr) {
    std::this_thread::sleep_for(seconds{5});
    sptr->display();
}

void thread_two(std::shared_ptr<x> sptr) {
    sptr->display();
    std::this_thread::sleep_for(seconds{5});
}


void learn_shared_ptr(void) {
    std::shared_ptr<x> sx{new x};
    std::cout<<"Object Reference Count: "<<sx.use_count()
         <<std::endl;
    std::thread tx{thread_one, sx};
    std::cout<<"Object Reference Count: "<<sx.use_count()
         <<std::endl;
    std::thread ty{thread_two, sx};
    std::cout<<"Object Reference Count: "<<sx.use_count()
         <<std::endl;
    tx.join();
    ty.join();
    std::cout<<"Object Reference Count: "<<sx.use_count()
         <<std::endl;
}

int main(int argc, const char* argv[]) {
    learn_shared_ptr();
    return 0;
}

//Code Ends



When I run the above program, I get the following output. Watch out for reference count value depending on the usage.




//Console Output Begins

$ ./test
x::x()
Object Reference Count: 1
Object Reference Count: 2
Object Reference Count: 3
Sachin Is SuperMan
Sachin Is SuperMan
Object Reference Count: 1
x::~x()

//Console Output Ends

No comments:

Post a Comment