16
Oct
2021

Smart Pointers (4/6): How to Create Our Own Shared Pointer

 

We’ll write our basic smart shared pointer class to understand very well the std::shared_pointer class.

We base our functions on the std::shared_ptr class to select our member functions:

 

Std::shared_ptr has a control block that contains reference counter, weak counter, allocator, and custom deleter. But we design our own basic Shared pointer. That’s why we only use reference counter from within the control block. The other pointer is, we will use, owned object pointer.

Our shared pointer class provides these features:

  • Has to be generic using the template
  • Has to provide their functions for const objects except for the transfer and reset functions.

Okay, all codes will be coded with C++17 standards.

Here is the basic shared pointer class:

#ifndef SHAREDPOINTER_H
#define SHAREDPOINTER_H

template < typename T>
  class SharedPointer
  {
    public_colon
    SharedPointer():
      ptr(nullptr),
      refCount(new long(0)) {};

    SharedPointer(T *ptr_): ptr(ptr_), refCount(new long(1)) {};	//refcount is 1 now

    ~SharedPointer()
    {
      if (this->ptr)	// if ptr is not nullptr we have to break the owner
      {
        (*this->refCount) --;

        if (*this->refCount == 0)	// there is no another owner, we need to delete the resource and refCount
        {
          delete this->ptr;
          delete this->refCount;

          this->ptr = nullptr;
          this->refCount = nullptr;
        }
      }
    }

    SharedPointer(const SharedPointer &other)	//copy constructor
    {
      this->ptr = other.ptr;
      this->refCount = other.refCount;

      if (refCount != nullptr)
        (*this->refCount) ++;

    };

    SharedPointer &operator=(const SharedPointer &other)
    {
    	//copy assignment

      if (this != &other)	//cheking self assignment
      {
        this->ptr = other.ptr;
        this->refCount = other.refCount;

        if (refCount != nullptr)
          (*this->refCount) ++;
      }

      return * this;
    };

    SharedPointer(SharedPointer && o)	//move constructor
    {
      this->ptr = o.ptr;
      this->refCount = o.refCount;

      o.ptr = nullptr;
      o.refCount = nullptr;

    }

    SharedPointer &operator=(SharedPointer && o)
    {
    	// move assignment
      this->ptr = o.ptr;
      this->refCount = o.refCount;

      o.ptr = nullptr;
      o.refCount = nullptr;
      return * this;

    }

    T *operator->() const
    {
      return this->ptr;
    }

    T &operator* ()const
    {
      return *(this->ptr);
    }

    T* get() const
    {
      return this->ptr;
    }

    long use_count() const
    {
      if (refCount != nullptr)
        return * refCount;
      else return 0;
    }

    operator bool() const
    {
      if (nullptr != this->ptr)
        return true;
      else
        return false;
    };

    void reset(T *ptr_ = nullptr)
    {
      if (this->ptr)	// if ptr is not nullptr we have to break the ownership
      {
        (*this->refCount) --;

        if (*this->refCount == 0)	// there is no another owner, we need to delete the resource and refCount
        {
          delete this->ptr;
          delete this->refCount;

          this->ptr = nullptr;
          this->refCount = nullptr;
        }
      }

      this->ptr = ptr_;	//assign the ptr_.
      this->refCount = new long(0);

      if (this->ptr)
        (*this->refCount) ++;
    }

    private:
      T * ptr;
    long * refCount;

  };

#
endif	// SHAREDPOINTER_H

Shall we take a brief look at the class and its functions?

The owned object pointer (T*ptr) and reference counter pointer (long* refCount) are encapsulated as a private member.

The constructors:

In general, we initialize the owned object and refCount pointer inside constructors.

But, inside the copy constructor we do deep copy pointers from SharedPointer &other to (*this).

Inside the move constructor we transfer all pointers to (*this), then, we assign the nullptr to moved pointers.

The destructor checks if the owned object ptr is nullptr or not, then, decrease the reference counter (refCount). If the reference counter reaches zero, it means there is no other ownership. The destructor deletes the resource and reference counter pointer.

Operator= overload function for copying semantic is that provides to copy the object, then, increase the reference counter and returns (*this).

Operator= overload function for moving semantic is that provides to move the object, then, assign the null pointer to them and returns (*this).

Operator-> and operator* provide access to resources owned by *this

The function named get, provides access to a resource

The function named use_count, returns the reference counter.

The function named reset, gives two options to the user. One of them is only breaking ownership by calling the function without parameters. The other is that change the ownership with a new resource. We can think that is combined with constructor and destructor.

Operator bool function checks if the object owns a resource or not.

Here is the main function that we test our shared pointer class:

#include <iostream>
#include "sharedpointer.h"
using namespace std;

class embeddedWorld
{
  public:
	 embeddedWorld(int p_no): planetNo(p_no)
	 {// for debugging
	  std::cout << "inside embeddedWorld constructor: planet No: " << planetNo << std::endl;
	 }	

	~embeddedWorld()
	{// for debugging
	  std::cout << "inside embeddedWorld Destructor: planet No: " << planetNo << std::endl;
	}	

	int getPlanetNo() const
	{
		return planetNo;
	};

	private:
		int planetNo = 0;
};

int main()
{

   {
   SharedPointer<embeddedWorld> e1(new embeddedWorld(10));

   SharedPointer<embeddedWorld> e2;
   e2 = e1;

   SharedPointer<embeddedWorld> e3(e2);

   cout << "e3.use_count(): " << e3.use_count() << endl;
   e3.reset(new embeddedWorld(12));
   cout << "e3.use_count(): " << e3.use_count() << endl;
   cout << "e2.use_count(): " << e2.use_count() << endl;

   SharedPointer<embeddedWorld> e4(new embeddedWorld(20));
   SharedPointer<embeddedWorld> e5 = std::move(e4);

   SharedPointer<embeddedWorld> e6;
   e6 = std::move(e5);

   //test: reset function
   SharedPointer<embeddedWorld> e7(new embeddedWorld(10));
   cout << "e7 reference count before reset:" << e7.use_count() << endl;
   e7.reset();
   cout << "e7 reference count after reset:" << e7.use_count() << endl;
   }

   return 0;
}

The output:
inside embeddedWorld constructor: planet No: 10
e3.use_count(): 3
inside embeddedWorld constructor: planet No: 12
e3.use_count(): 1
e2.use_count(): 2
inside embeddedWorld constructor: planet No: 20
inside embeddedWorld constructor: planet No: 10
e7 reference count before reset:1
inside embeddedWorld Destructor: planet No: 10
e7 reference count after reset:0
inside embeddedWorld Destructor: planet No: 20
inside embeddedWorld Destructor: planet No: 12
inside embeddedWorld Destructor: planet No: 10