Smart pointers in C++ are a wrapper class. It is used to manage dynamically allocated memory and to ensure that the memory gets deleted when the smart pointer object goes out of scope. Smart pointers are just classes that wrap the raw pointer and offer the same syntax (overloaded -> and * operators) as a raw pointer.

C++11 has following types of smart pointers that are defined in header memory of the Standard Library

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

Unique Pointer

An​ unique_ptr has exclusive ownership of the object it points to and ​will destroy the object when the pointer goes out of scope. It prevents copying of its contained pointer. Instead, the std::move function has to be used to transfer ownership of the contained pointer to another unique_ptr. When this object is destructed then in its destructor it deletes the associated raw pointer.

Syntax to declare a unique pointer is :

std::unique_ptr<Type> p(new Type);

unique_ptr has its -> and * operator overloaded, so it can be used similar to normal pointer.

Important member functions

  • get() : Returns a pointer to the managed object or nullptr if no object is owned.
  • release() : Releases the ownership of the managed object if any. The caller is responsible for deleting the object.
  • reset() : Replaces the managed object. It performs the following actions, in this order:
    1. Saves a copy of the current pointer.
    2. Overwrites the current pointer with the argument.
    3. If the old pointer was non-empty, deletes (get_deleter()) the previously managed object
  • swap() : Swaps the managed objects and associated deleters of *this and another unique_ptr object other.

Below example shows the application of unique pointer

#include <memory>
#include <iostream>
using namespace std;

void print_value(std::unique_ptr<int> p) {
  cout<< *p << endl;
}

void print_ref(std::unique_ptr<int>& p) {
  cout<< *p << endl;
}

struct Foo {
  Foo(int _val) : val(_val) { 
    std::cout << "Foo...\n"; 
  }

  ~Foo() { 
    std::cout << "~Foo...\n"; 
  }

  int val;
};

int main()
{
  std::unique_ptr<int> ptr(new int(401));

  // ERROR, since copy of unique ptr is not allowed
  // std::unique_ptr<int> ptr_copy = ptr;

  // ERROR since arguments are passed by value(copy) to function.
  //print_value(ptr); 

  print_ref(ptr);

  // 1. Get pointer to the managed object 
  int *s = ptr.get();
  std::cout << *s << '\n';  // Output : 401

  // 2. Copy the pointer
  std::unique_ptr<int> ptr_copy = std::move(ptr);

  // 3. Release the managed object
  std::unique_ptr<Foo> up(new Foo(1));
  Foo* fp = up.release();

  // Foo is no longer owned by unique_ptr
  delete fp;

  std::unique_ptr<Foo> up1(new Foo(2));

  // 4. Replace owned Foo with a new Foo. Calls deleter for the old one
  up1.reset(new Foo(3));  

  // Release and delete the owned Foo
  up1.reset(nullptr);

  std::unique_ptr<Foo> up1(new Foo(4));
  std::unique_ptr<Foo> up2(new Foo(5));

  // 5. Swap pointer
  up1.swap(up2);

  std::cout << "up1->val:" << up1->val << std::endl;
  std::cout << "up2->val:" << up2->val << std::endl;
}

Shared Pointer

Shared pointer owns the object it points to but, unlike the unique_ptr, it allows for multiple references. It has an internal counter (reference counting) which is decreased each time that a std::shared_ptr, pointing to the same resource, goes out of scope. When the last shared pointer goes out of scope, memory is released automatically.

Syntax to declare a shared pointer is :

std::shared_ptr<Type> p(new Type);

shared_ptr has its -> and * operator overloaded, so it can be used similar to normal pointer.

Important member functions

  • use_count() : Returns the number of different shared_ptr instances (this included) managing the current object. If there is no managed object, ​0​ is returned. In multithreaded environment, the value returned by use_count is approximate.
  • get() : Returns the stored pointer.
  • swap() : Swaps the managed objects and associated deleters of *this and another shared_ptr object other.
  • reset() : Replaces the managed object with an object. Optional deleter d can be supplied, which is later used to destroy the new object when no shared_ptr objects own it.

Below example shows the application of shared pointer

#include <memory> 
#include <iostream> 
#include <string>

using namespace std;

void print(std::shared_ptr<int> sp)
{
  std::cout << "sp.use_count() == " << sp.use_count() << '\n'; 
}
 
int main() 
{ 
  // 1. use_count
  auto sp1 = std::make_shared<int>(5);

  // Output : 1
  std::cout << "sp1.use_count() == " << sp1.use_count() << '\n'; 

  // Output : 2
  print(sp1);

  // 2. get()
  std::shared_ptr<int> pShared = std::make_shared<int>(42);
  int* pInt = pShared.get();

  std:cout<< *pInt << "\n";

  delete pInt;

  // 3. Swap
  std::shared_ptr<int> sp2 = std::make_shared<int>(2);
  std::shared_ptr<int> sp3 = std::make_shared<int>(20);

  sp2.swap(sp3);

  std::cout << "*sp2 : " << *sp2 << std::endl;
  std::cout << "*sp3 : " << *sp3 << std::endl;

  // 4. Reset
  std::shared_ptr<int> sp4 = std::make_shared<int>(1);
 
  // reset the shared_ptr, hand it a fresh instance of Foo
  sp4.reset(new int);
}

Weak Pointer

A weak pointer is like a std::shared_ptr except that it doesn’t increase its reference count. It is defined as a smart pointer that holds a non-owning (weak) reference to an object that is managed by another std::shared_ptr. The shared_ptr holds the real object, whereas​ the weak_ptr uses a lock to connect to the real owner and returns NULL otherwise.

Syntax to declare a weak pointer is :

std::weak_ptr<int> p_weak1(p_shared);

weak_ptr has its -> and * operator overloaded, so it can be used similar to normal pointer.

Important member functions

  • lock() : Creates a new std::shared_ptr that shares ownership of the managed object. If there is no managed object, then the returned shared_ptr also is empty.
  • swap() : Swaps the managed objects and associated deleters of *this and another shared_ptr object other.
  • reset() : Releases the reference to the managed object. After the call *this manages no object.
  • use_count() : Returns the number of shared_ptr instances that share ownership of the managed object, or ​0​ if the managed object has already been deleted.
  • expired() : It is equivalent to use_count() == 0. The destructor for the managed object may not yet have been called, but this object’s destruction is imminent.
#include <iostream>
#include <memory>
using namespace std;

std::weak_ptr<int> gw;

void fun(std::shared_ptr<int> sp){

  std::weak_ptr<int> wp {sp};
  cout<<"Count : "<< wp.use_count()<<endl;
}
 
void print()
{
  if (!gw.expired()) {
    std::cout << "gw is valid\n";
  }
  else {
      std::cout << "gw is expired\n";
  }
}

int main() {

  std::shared_ptr<int> p1(new int(23));

  // 1. Using lock()
  // Make a weak pointer to p1
  std::weak_ptr<int> wp1 {p1};
  {
    // Now p1 and p2 own the memory.
    std::shared_ptr<int> p2 = wp1.lock();
    if (p2) {
      cout<< "Weak Value : " <<*p2<<endl;
    }
  }

  // p2 gets destroyed since out of scope. only p1 holds the memory now

  // Delete p1.
  p1.reset(); 


  // 2. use_count()
  std::shared_ptr<int> p2(new int(25));
  std::shared_ptr<int> p3 = p2;

  // Output : 3
  fun(p3);

  // 3. expired()
  {
    auto sp = std::make_shared<int>(42);
    gw = sp;

    print();
  }

  print();
}

When to use Smart Pointer

  • Use std::unique_ptr when you don’t intend to hold multiple references to the same object. For example, use it for a pointer to memory which gets allocated on entering some scope and de-allocated on exiting the scope.
  • 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.
  • 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.