Smart pointers in C++ 11

Discussion in 'C++' started by BiplabKamal, Jan 31, 2016.

  1. Releasing memory allocated in heap has been always been a concern for programming languages like C and C++. Memory leak is a side effect of using dynamic memory allocation in C++. This is because C++ compiler does not support automatically release memory allocated in heap when they are no more required. Languages such as Java and C# has this facility but with the overhead of running a garbage collector.

    C++ is an efficient programming language and generate high performing programs. It needs to maintain generating efficient code but also need to solve the problem of memory leak. C++ standard library came to rescue the programmer with an efficient way of dealing with memory leak problem with the concept of smart pointer. Smart pointers are template classes. Smart pointers are class objects but behaves like a raw pointer. Advantage of smart pointers is that you need not to bother about the deletion of the raw pointer. Smart pointers manage objects created in heap and automatically deletes the managed object at the appropriate time.

    You can use smart pointers like a raw pointers by using pointer operators like - > and *. Prior to C++11 std::auto_ptr class provided this facility where an instance(in stack memory) of class std::auto_ptr owns the pointer and manage the releasing of the memory. std::auto_ptr allows single owner for an object. Destructor of the owner object releases the heap memory allocated for the managed object. Following code snippet shows how auto_ptr class used to work :

    EXAMPLE:

    Code:
    #include<memory> // smart pointer classes are defined in <memory> header
    #include<iostream>
    using namespace std;
    class Myclass
    {
    private:
        static int instancecount;
        int data;
    public:
        Myclass()
        {
            data = 0;
            cout << "Constructor of Myclass" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
        }
        ~Myclass()
        {
            cout << "Destructor of Myclass" << endl;
            cout << "Number of instances: " << --instancecount << endl;
        }
        void DoSomething()
        {
            cout << "Doing something" << endl;
        }
    };
    
    int Myclass::instancecount = 0;
    
    int main()
    {
        auto_ptr<Myclass> p1(new Myclass()); //Create auto_ptr object and assign the ownership of newly allocated object
        p1->DoSomething();
        auto_ptr<Myclass> p2 = p1; // Ownership of the object transferred from p1 to p2. Here p1 looses the owneship
        p2->DoSomething();
        //p1->DoSomething();// Will give debug assertion as the p1 already transferred the ownership to p2 and pointer value with p1 is null
    
    } // Will delete the pointer owned by p2 when it goes out of scope
    
    Here is the output of the program in Visual Studio 2015
    Code:
    Constructor of Myclass
    Number of instances: 1
    Doing something
    Doing something
    Destructor of Myclass
    Number of instances: 0
    
    If you look into the above code closely you will find that the 3rd line in the main() function transfer the ownership of the pointer implicitly. Here the copy constructor of the auto_ptr class copy the pointer to new object and remove the pointer from the original object. The problem here is that you normally don't expect the copy constructor and copy assignment operator to modify the original object. In the 5th line of the main function will not work because the pointer has been set to null. This problem will be more serious if some class declare a member variable of type auto_ptr and copy constructor and copy assignment operator of the class are enabled. In this case when an object is initialized with an existing object the state of the existing object is changed which is not expected.

    Another problem with auto_ptr is that it does not support a pointer to array of objects. You cannot create an auto_ptr with a pointer to an array of objects. Following lines of code is not valid -

    auto_ptr<int[]> p(new int[10]);

    With move semantics introduced in C++11/C++14 above problems with auto_ptr are solved. It introduced three new types of smart pointers and deprecated use of auto_ptr. New template classes for smart pointers are unique_ptr,shared_ptr and weak_ptr which helps to use smart pointers in different ways.

    std::unique_ptr

    std::unique_ptr is a template class and a replacement for auto_ptr which is deprecated and will be removed in C++17. Copy constructor and copy assignment operator are deleted and std::move() function is added to transfer the ownership. Std::move() makes the transfer of ownership of the underlying resource explicit. Only one owner is allowed for std::unique_ptr and it can also own a pointer to an array of objects. std::unique_ptr is small and efficient. Size is only one pointer size and - > and * operators are as fast as using a raw pointer.

    Following example code will show the usage of std::unique_ptr template class

    EXAMPLE:

    Code:
    #include<memory>
    #include<iostream>
    using namespace std;
    class Myclass
    {
    private:
        static int instancecount;
        int value;
    public:
        Myclass()
        {
            value = 0;
            cout << "New instance of Myclass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
        }
        ~Myclass()
        {
            cout << "One instance of Myclass is destroyed" << endl;
            cout << "Number of instances: " << --instancecount << endl;
        }
        void AddValue(int v)
        {
            value += v;
            cout << "New value: "<<value << endl;
        }
    };
    
    int Myclass::instancecount = 0;
    
    int main()
    {
    
        unique_ptr<Myclass> p1(new Myclass());
        p1->AddValue(10);
        //unique_ptr<Myclass> p2(p1); // Copy constructor is deleted
        //unique_ptr<Myclass> p2 = p1;//Copy constructor is deleted
        unique_ptr<Myclass> p2;
        //p2 = p1; // copy assignment operator is deleted
        cout << "Moving ownership from p1 to p2" << endl;
        p2 = std::move(p1); // Ownership of the object transferred from p1 to p2 and p1 is pointing to nullptr
        //p1->AddValue(10); // p1 now points to nullptr and will get run-time error
        p2->AddValue(10);
        cout << "p1 pointing to " << p1.get()<<endl; // get()returns the underlying raw pointer which is nullptr in this case
        cout << "p2 pointing to " << p2.get()<<endl;
        p2.reset(new Myclass()); //releases the underlying resource and point to new resource
        cout << "pointer value of p2 after reset" << p2.get() << endl;
        Myclass* ptr =p2.release();// releases the owner ship of the underlying resource and return the raw pointer of the resource. 
        cout << "pointer value of p2 after release old ptr "<<ptr <<": " << p2.get() << endl;
            
        unique_ptr<Myclass[]> p3(new Myclass[3]); //Pointing to an array of objects
        
        return 0;
    }// Will delete the objects owned by p2 and p3 automatically when smart pointers goes out of scope. Notice that there is a memory leak due to raw pointer ptr not being deleted
    
    Here is the output of the program in Visual Studio 2015
    Code:
    New instance of Myclass is created
    Number of instances: 1
    New value: 10
    Moving ownership from p1 to p2
    New value: 20
    p1 pointing to 00000000
    p2 pointing to 005708A0
    New instance of Myclass is created
    Number of instances: 2
    One instance of Myclass is destroyed
    Number of instances: 1
    pointer value of p2 after reset005704E8
    pointer value of p2 after release old ptr 005704E8: 00000000
    New instance of Myclass is created
    Number of instances: 2
    New instance of Myclass is created
    Number of instances: 3
    New instance of Myclass is created
    Number of instances: 4
    One instance of Myclass is destroyed
    Number of instances: 3
    One instance of Myclass is destroyed
    Number of instances: 2
    One instance of Myclass is destroyed
    Number of instances: 1
    
    Using std::make_unique() template function and 'auto' keyword you can easily create unique_ptr.
    Code:
    #include<memory>
    #include<iostream>
    using namespace std;
    struct Myclass
    {
    private:
        static int instancecount;
        int value;
    public:
        Myclass()
        {
            value = 0;
            cout << "New instance of Myclass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
        }
        ~Myclass()
        {
            cout << "One instance of Myclass is destroyed" << endl;
            cout << "Number of instances: " << --instancecount << endl;
        }
        void AddValue(int v)
        {
            value += v;
            cout << "New value: " << value << endl;
        }
    };
    
    int Myclass::instancecount = 0;
    
    int main()
    {
        auto p1 = std::make_unique<Myclass>(); // Create an unique_ptr for single object
        auto p2 = std::make_unique<Myclass[]>(3); // Create an unique_ptr for an array of 3 objects
        p1->AddValue(10);
        p2[0].AddValue(10);
        auto p3 = std::move(p1);// Ownership of the object transferred from p1 to p3 and p1 is pointing to nullptr
        p3->AddValue(10);
        return 0;
    }// Will delete the objects owned by p2 and p3 automatically when smart pointers goes out of scope. 
    
    Output:
    Code:
    New instance of Myclass is created
    Number of instances: 1
    New instance of Myclass is created
    Number of instances: 2
    New instance of Myclass is created
    Number of instances: 3
    New instance of Myclass is created
    Number of instances: 4
    New value: 10
    New value: 10
    New value: 20
    One instance of Myclass is destroyed
    Number of instancess: 3
    One instance of Myclass is destroyed
    Number of instances: 2
    One instance of Myclass is destroyed
    Number of instances: 1
    One instance of Myclass is destroyed
    Number of instances: 0
    
    Following example will show how a member variable smart pointer of a class is initialized-
    Code:
    #include<memory>
    #include<string>
    #include<iostream>
    using namespace std;
    struct Myclass
    {
    private:
        static int instancecount;
        unique_ptr<string> Name;
    public:
        Myclass(string s): Name(make_unique<string>(s)) // Creating the member which is a smart pointer
        {
            cout << "New instance of Myclass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
        }
        ~Myclass()
        {
            cout << "One instance of Myclass is destroyed" << endl;
            cout << "Number of instances: " << --instancecount << endl;
        }
        void SetName(string s)
        {
            cout << "Old name: " << Name->c_str() << endl;
            Name.reset(new string(s));
            cout << "New name: " << Name->c_str() << endl;
        }
    };
    
    int Myclass::instancecount = 0;
    
    int main()
    {
        auto p1 = std::make_unique<Myclass>("Biplab"); // Create an unique_ptr for single object
        p1->SetName("Anitha");
        return 0;
    }// Will delete the objects owned by p1 automatically when smart pointers goes out of scope. 
    
    
    For more about class members of std::unique_ptr, see http://en.cppreference.com/w/cpp/memory/unique_ptr

    std::shared_ptr

    shared_ptr enables you to assign multiple owners of a resource pointer or handler. For managing the destruction of the resource a separate control block(manager object) is introduced which maintains the reference count of the owners and delete the pointer when reference count is zero. For reference counting and managing the lifetime of the resource a control block is added to the memory block (manager object) allocated for the resource. The size of the shared_ptr object is two pointers one the for the managed object and the other for the control block. When the first shared_ptr get the owner ship of the managed object it creates the control block (manager object) and share it across shared_ptrs who become owners later. You can construct a shared_ptr object by passing the object pointer to the constructor of the template class or by calling std::make_shared() function. Following 2 lines produce same result -

    shared_ptr<int> ptr(new int(10));

    auto ptr = make_shared<int>(10);

    When a shared_ptr object become a shared owner of a managed object the object manager increases the reference count. Constructor, copy constructor and copy assignment operator assign the ownership by increasing the reference count. When shared_pointer object goes out of scope the destructor decreases the reference count. Also if the owner smart pointer releases the underlying object explicitly the reference count decreases. When the reference count is zero the managed object is destroyed but the manager object still may be there because of weak_ptr reference count which will be discussed later. Manager object get destroyed only when shared reference count and weak reference count both are zero. Following example will demonstrate shared_ptr creation, deletion and reference counting.

    Code:
    #include<memory>
    #include<string>
    #include<iostream>
    using namespace std;
    struct Myclass
    {
    private:
        static int instancecount;
        unique_ptr<string> Name;
    public:
        Myclass(string s): Name(make_unique<string>(s))
        {
            cout << "New instance of Myclass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
        }
        ~Myclass()
        {
            cout << "One instance of Myclass is destroyed" << endl;
            cout << "Number of instances: " << --instancecount << endl;
        }
        void SetName(string s)
        {
            cout << "Old name: " << Name->c_str() << endl;
            Name.reset(new string(s));
            cout << "New name: " << Name->c_str() << endl;
        }
    };
    
    int Myclass::instancecount = 0;
    
    int main()
    {
        //Printing the sizes of different smart pointers
        cout << "Size of auto_ptr : " << sizeof(std::auto_ptr<int>)<<endl; // Size is one pointer
        cout<<"Size of unique_ptr : "<<sizeof(std::unique_ptr<int>) << endl; // Size is one pointer
        cout << "Size of shared_ptr : " << sizeof(std::shared_ptr<int>) << endl; // size is 2 pointer
        
        //Creating shared_ptr in different ways
        shared_ptr<Myclass> p1(new Myclass("Bidisha"));// Create shared_ptr by constructor. Reference count 1
        cout << "p1 reference count = " << p1.use_count() << endl;
        shared_ptr<Myclass> p3 = p1; //Initialize shared_ptr by copy constructor. Increasing the reference count
        cout << "p1 reference count = " << p1.use_count() << endl;
        shared_ptr<Myclass> p4(p1);//Initialize shared_ptr by copy constructor. Increasing reference count
        cout << "p1 reference count = " << p1.use_count() << endl;
        shared_ptr<Myclass> p5;//empty smart pointer
        p5 = p1; //Own a new object by assignment operator. Increasing reference count
        cout << "p1 reference count = " << p1.use_count() << endl<<endl;
        
        // Showing impact of creating and destroying smart pointers
        cout << "Loop start" << endl;
        for (int i = 0; i < 3; i++)
        {
            cout << "p1 reference count = " << p1.use_count() << endl;
            auto p2 = p1;// Increasing the reference count
            cout << "p1 reference count = " << p1.use_count() << endl;
        }// decreasing reference count as p2 goes out of scope
        cout << "p1 reference count = " << p1.use_count() << endl;
        cout << "Loop end" << endl << endl;
        p5.reset(); // Decreases the reference count to 0 assign nullptr to the underlying object
        cout << "p1 reference count = " << p1.use_count() << endl;
        cout << ""<< endl;
            
        return 0;
    }// Will delete the objects owned by p1 automatically when smart pointers goes out of scope. 
    
    Code:
     Size of auto_ptr : 4
    Size of unique_ptr : 4
    Size of shared_ptr : 8
    New instance of Myclass is created
    Number of instances: 1
    p1 reference count = 1
    p1 reference count = 2
    p1 reference count = 3
    p1 reference count = 4
    
    Loop start
    p1 reference count = 4
    p1 reference count = 5
    p1 reference count = 4
    p1 reference count = 5
    p1 reference count = 4
    p1 reference count = 5
    p1 reference count = 4
    Loop end
    
    p1 reference count = 3
    
    One instance of Myclass is destroyed
    Number of instancess: 0
    
    For more about class members see http://en.cppreference.com/w/cpp/memory/shared_ptr

    std::weak_ptr

    weak_ptr can be considered one kind of shared_ptr but does not participate in the ownership. That means it can observe the managed resource but cannot own it. Managed object creation and deletion does not depend on weak_ptr reference. weak_ptr object is created from a shared_ptr object but it does not increase the reference count. The manager object (control block) keep a track of weak_ptr reference count but don't depend on it for deleting the resource. Does that mean that weak_ptr can have a dangling pointer? That's right but it ensures that there is a check for that. The control block (manager object) which keeps track of both shared reference count and weak reference count can check if the managed object exists or not. One important thing to note that the managed object and the control block is allocated in the same memory allocation unit. So when the shared count is zero the manged object is destroyed and the destructor is called but the memory cannot be freed until the control block is also released when the weak count is also zero.

    Following code snippet shows how weak_ptr works-

    Code:
    #include<memory>
    #include<string>
    #include<iostream>
    using namespace std;
    struct Myclass
    {
    private:
        static int instancecount;
        unique_ptr<string> Name;
    public:
        Myclass(string s): Name(make_unique<string>(s))
        {
            cout << "New instance of Myclass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
        }
        ~Myclass()
        {
            cout << "One instance of Myclass is destroyed" << endl;
            cout << "Number of instances: " << --instancecount << endl;
        }
        void SetName(string s)
        {
            cout << "Old name: " << Name->c_str() << endl;
            Name.reset(new string(s));
            cout << "New name: " << Name->c_str() << endl;
        }
    };
    
    int Myclass::instancecount = 0;
    
    int main()
    {
        cout << "Size of weak_ptr : " << sizeof(std::weak_ptr<int>) << endl; // size is 2 pointer
        
        //Creating weak_ptr in different ways
        shared_ptr<Myclass> p1(new Myclass("Bidisha"));// Create shared_ptr object first
        weak_ptr<Myclass> p2 = p1; //Creat a weak_ptr object from share_ptr object. Increasing the weak reference count but not shared count
        weak_ptr<Myclass> p3(p2);//Create weak_ptr from a weak ptr. Increasing weak reference count but not shared count
    
        cout << "shared reference count = " << p2.use_count() << endl;
        auto p4 = p2.lock();// increase shared reference count
        cout << "shared reference count = " << p2.use_count() << endl;
        p1 = nullptr;// decreasing shared reference count
    //    p2 = nullptr;// not valid
        cout << "shared reference count = " << p2.use_count() << endl;
        cout << "shared reference count = " << p2.use_count() << endl;
        if (p2.expired())
            cout << "weak ptr p2 expired"<<endl;
        else
            cout << "weak ptr p2 is not expired" << endl;
        p4 = nullptr; // decreasing the shared reference count
        if (p2.expired())
            cout << "weak ptr p2 expired" << endl;
        else
            cout << "weak ptr p2 is not expired" << endl;
        
        return 0;
    }// all smart pointers goes out of scope and destructors are called which decrement the reference counts. Shared reference count will be zero and the object is deleted. Weak reference count also will be zero and the control block object also will be deleted
    
    Output:
    Code:
    Size of weak_ptr : 8
    New instance of Myclass is created
    Number of instances: 1
    shared reference count = 1
    shared reference count = 2
    shared reference count = 1
    shared reference count = 1
    weak ptr p2 is not expired
    One instance of Myclass is destroyed
    Number of instances: 0
    weak ptr p2 expired
    
    For more about class members see http://en.cppreference.com/w/cpp/memory/weak_ptr

    In modern C++ raw pointers are rarely used. They should be only used when there is no confusion about the ownership of the object like in small blocks of code or small function. Remember following steps while using smart pointers
    1. Smart pointers should declared as local variable. Do not allocate the smart pointer itself on the heap.
    2. Pass the newly created raw pointer to the constructor of the smart pointer object or use make functions
    3. To access the underlying object use overloaded operators - > and *.
    Though the smart pointers are great help to manage resource allocation and deallocation you need to remember that this is not a compiler feature. The smart pointer classes are just like user defined classes and they may not be full proof. You may create a mess if you don't understand the implication in detail. Look into the following code :

    Code:
    #include<memory>
    #include<iostream>
    using namespace std;
    class MyClass
    {
    private:
        static int instancecount;
        int data;
    public:
        MyClass()
        {
            data = 0;
            cout << "New instance of MyClass is created" << endl;
            cout << "Number of instances: " << ++instancecount << endl;
        }
        ~MyClass()
        {
            cout << "One instance of MyClass is destroyed" << endl;
            cout << "Number of instances: " << --instancecount << endl;
        }
        
    };
    int MyClass::instancecount = 0;
    int main()
    {
        MyClass *pMyClass = new MyClass(); // Create the raw pointer
        // Create 2 unique_ptr objects from existing raw pointer
        unique_ptr<MyClass> pUnique1(pMyClass);
        unique_ptr<MyClass> pUnique2(pMyClass);
        return 0;
    }//Smart pointers will go out of scope and try to delete the underlying raw pointer
    
    Output:
    Code:
    New instance of MyClass is created
    Number of instances: 1
    One instance of MyClass is destroyed
    Number of instances: 0
    One instance of MyClass is destroyed
    Number of instances: -1
    
    In the above code the managed object is getting destroyed 2 times by the 2 unique_ptr who own the same object. Here the learning is that you should not create a stand alone raw pointer rather you should create the new raw pointer in the constructor of the smart pointer.
     
    shabbir likes this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice