C++ Smart pointers


What is smart pointer?

In modern C++ programming, the Standard Library includes smart pointers, which are used to help ensure that programs are free of memory and resource leaks and are exception-safe.

A smart pointer is a class template that you declare on the stack, and initialize by using a raw pointer that points to a heap-allocated object. After the smart pointer is initialized, it owns the raw pointer. This means that the smart pointer is responsible for deleting the memory that the raw pointer specifies. The smart pointer destructor contains the call to delete, and because the smart pointer is declared on the stack, its destructor is invoked when the smart pointer goes out of scope, even if an exception is thrown somewhere further up the stack.

Access the encapsulated pointer by using the familiar pointer operators, -> and *, which the smart pointer class overloads to return the encapsulated raw pointer.

The C++ smart pointer idiom resembles object creation in languages such as C#: you create the object and then let the system take care of deleting it at the correct time. The difference is that no separate garbage collector runs in the background; memory is managed through the standard C++ scoping rules so that the runtime environment is faster and more efficient.

Smart pointers are defined in the std namespace in the <memory> header file.


A modern C++ idiom:

RAII: Resource Acquisition Is Initialization.

● When you initialize an object, it should already have acquired any resources it needs (in the constructor).

● When an object goes out of scope, it should release every resource it is using (using the destructor).


Example:


class LargeObject
{
    public:   
         void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{         
    // Create the object and pass it to a smart pointer     
    std::unique_ptr<LargeObject> pLarge(new LargeObject());     

    //Call a method on the object     
    pLarge->DoSomething();     

    // Pass a reference to a method.     
    ProcessLargeObject(*pLarge); 

} //pLarge is deleted automatically when function block goes out of scope.


The example demonstrates the following essential steps for using smart pointers.

  1. Declare the smart pointer as an automatic (local) variable. (Do not use the new or malloc expression on the smart pointer itself.)
  2. In the type parameter, specify the pointed-to type of the encapsulated pointer.
  3. Pass a raw pointer to a new-ed object in the smart pointer constructor. (Some utility functions or smart pointer constructors do this for you.)
  4. Use the overloaded -> and * operators to access the object.
  5. Let the smart pointer delete the object.


Types of smart pointers:

1) Unique_ptr:

NOTES:

  1. unique_ptr is a class template.
  2. unique_ptr is one of the smart pointer provided by c++11 to prevent memory leaks.
  3. unique_ptr wraps a raw pointer in it, and de-allocates the raw pointer, when unique_ptr object goes out of scope.
  4. similar to actual pointers we can use -> and * on the object of unique_ptr, because it is overloaded in unique_ptr class.
  5. when exception comes then also it will de-allocate the memory hence no memory leak.
  6. Not only object we can create array of objects of unique_ptr.

OPERATIONS:

    - release, reset, swap, get, get_deleter.

class Foo 
{                 
    int x;                 
public:                               
    explicit Foo(int x): x{x} {}                             
    int getX() { return x; } 
};     

int main() 
{                 
    unique_ptr<Foo> p1 (new Foo(10));             
    unique_ptr<Foo> p2 = make_unique<Foo>(20); // it is used for deleting memory incase of exceptions.                                 
    cout << p1->getX() << (*p2).getX() << endl;                                 
    // p1 = p2; FAIL: This will fail because you cannot copy ownership.                 
    unique_ptr<Foo> p3 = std::move(p1); // PASS: Because moving ownership is allowed.                                 

    Foo *p = p2.get();                                 
    
    Foo *p4 = p3.release();                       
    
    p2.reset(p4);                 
    return 0; 
}


2) Shared_ptr:

NOTES:
  1. shared_ptr is a smart pointer which can share the ownership of object (managed object).
  2. Several shared_ptr can point to the same object (managed object).
  3. It keep a reference count to maintain how many shared_ptr are pointing to the same object and once last shared_ptr goes out of scope then managed object gets deleted.
  4. shared_ptr is threads safe and not thread safe. [What is this??]
    a) control block is thread safe. (Which is used to store reference count).
    b) managed object is not thread safe.
  5. There are 3 ways shared_ptr will destroy managed object
    a) If the last shared_ptr goes out of scope.
    b) If you initialize shared_ptr with some other shared_ptr.
    c) If you reset shared_ptr.
  6. Reference count doesn't work when we use reference or pointer of shared_ptr.

3) Weak_ptr:

NOTES:
  1. If we say unique_ptr is for unique ownership and shared_ptr is for shared ownership then weak_ptr is for non-ownership smart pointer.
  2. It actually reference to an object which is managed by shared_ptr.
  3. A weak_ptr is created as a copy of shared_ptr.
  4. We have to convert weak_ptr to shared_ptr in order to use the managed object.
  5. It is used to remove cyclic dependency between shared_ptr.
Sample program:

int main()
{               
    auto sharedPtr = std::make_shared<int>(100);
    std::weak_ptr<int> weakPtr(sharedPtr);               

    cout << weakPtr.use_count() << endl;  // RC(Reference Count) : 1               
    cout << sharedPtr.use_count() << endl;  // RC(Reference Count) : 1               
    cout << weakPtr.expired() << endl;  // false               

    // NOTE: weak pointer will never increase reference count.           
    if ( std::shared_ptr<int> sharedPtr1 = weakPtr.lock() )   
    {                             
        cout << *sharedPtr << endl;                         
        cout << sharedPtr1.use_count() << endl; 
    }

    weakPtr.reset();               
    return 0;
}


4) Auto_ptr:

It's deprecated in C++11 and removed in C++17. Hence it's highly discouraged to use auto_ptr due to security issue.

It is based on exclusive ownership model i.e. two pointers of the same type can’t point to the same resource at the same time. As shown in the below program, copying or assigning of pointers changes the ownership i.e. source pointer has to give ownership to the destination pointer.

When to use which?

  1. Use std::unique_ptr when you want your object to live just as long as a single owning reference to it lives. For example, use it for a pointer to memory which gets allocated on entering some scope and de-allocated on exiting the scope.
  2. Use std::shared_ptr when you do want to refer to your object from multiple places - and do not want your object to be de-allocated until all these references are themselves gone.
  3. Use std::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).
  4. Don't use the boost:: smart pointers or std::auto_ptr it's highly discouraged.

Comments

Popular posts from this blog

KMP Algorithm: Pattern Searching in Text

Z-Function Algorithm: Substring Search

Back of the envelope estimations