14
Oct
2021

Smart Pointers (2/6): How to Create Our Own Unique Pointer

We’ll write our smart unique pointer class to understand the std::unique_ptr class very well. We have no other intended purpose. Let’s enjoy that and practice our core C++ skills.

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

In this way, the functions below are enough to create a  basic custom unique pointer class. If more is needed, it can be added whether the intended function exists at the std::unique_ptr class or not.

Additionally, our class provides these features:

  • Has to be generic using a template
  • Has to disable copy constructor and assignment
  • 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 unique smart pointer class:

#ifndef UNIQUEPOINTER_H
#define UNIQUEPOINTER_H

template < typename T>
class UniquePointer
{
	public:
	UniquePointer(): ptr(nullptr) {};
	UniquePointer(T *ptr_): ptr(ptr_) {};

	UniquePointer(const UniquePointer &u) = delete;
	UniquePointer &operator=(const UniquePointer &u) = delete;

	UniquePointer(UniquePointer && u)
	{
	   this->ptr = u.ptr;
	   u.ptr = nullptr;
	};

	UniquePointer &operator=(UniquePointer && u)
	{
	   this->ptr = nullptr;
	   this->ptr = u.ptr;
	   u.ptr = nullptr;
	   return * this;
	}

	~UniquePointer()
	{
           if (ptr != nullptr)
	    {
		delete ptr;
		ptr = nullptr;
            }
	};

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

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

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

	void reset(T *ptr_ = nullptr)
	{
	  T *old = this->ptr;

	  this->ptr = ptr_;

	  if (old) delete old;
	}

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

	void swap(UniquePointer & other)
	{
	  UniquePointer temp;
	  temp.ptr = other.ptr;
	  other.ptr = ptr;
	  ptr = temp.ptr;
	  temp.reset();
	}
	private:
	  T * ptr;
	};

#endif	// UNIQUEPOINTER_H

If we have a look at the class:
We encapsulated the type of T * pointer named ptr as a private member. That provides ownership.
Constructors:
The two constructors: one of them does not take the pointer and initializes the pointer as a nullptr. The other takes a pointer and initializes the pointer with the parameter.
Copy semantic functions are deleted to avoid copying.
Move semantic functions are defined. Move constructor provides transferring the resource from the unique pointer object u to *this. Then, it stores the null pointer in the object, u.
Move assignment operator function transfers resource from u to *this. This may perform when the unique pointer class exists. That’s why we have to clean the *this’s resource before the transferring.

The destructor deletes the resource if it’s not a null pointer.
Operator-> and operator* provide access to resources owned by *this

The function, named “get” provides access to a resource

The function, named “reset” replaces the managed resource. It takes the new resource pointer as a parameter and its default value is a null pointer that can be invoked without a parameter.

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

The function, named “swap” swaps the resources of *this and other. std::swap function may be used inside the function.

Question: The private member T*ptr is accessible by the move constructor and move assignment functions are also the function of swap. This can access the private data of each other. How can it be possible?

Answer: The access modifiers like the private, work on a class level, not on an object level. These objects, this and others can access each other private data.

Here is the main function where we tested our class inside.

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

int main()
{

	UniquePointer<int> u1(new int(5));
	UniquePointer<int> u2(new int(10));

	cout << " *u1.get(): " << *u1.get() << endl;
	cout << " *u2.get(): " << *u2.get() << endl;

	u1.swap(u2);

	cout << " *u1.get(): " << *u1.get() << endl;
	cout << " *u2.get(): " << *u2.get() << endl;

	u1 = std::move(u2);
	cout << " *u1.get(): " << *u1.get() << endl;

	if (u1) cout << "u1 resource is existed" << endl;
	if (u2) cout << "u2 resource is existed" << endl;

	UniquePointer<int> u3;
	u3 = std::move(u1);
	*u3 = 20;

	cout << " *u3.get(): " << *u3.get() << endl;

	//    UniquePointer<int> u4 =u3;	// Error: The copy constructor is deleted

	//    const UniquePointer<int> u5;
	//    u5=std::move(u3);	// Error: no viable overloaded '=' because of constness

	cout << "Hi! from  myEmbeddedWorld!" << endl;
	return 0;
}

Output:

*u1.get(): 5
*u2.get(): 10
*u1.get(): 10
*u2.get(): 5
*u1.get(): 5
u1 resource is existed
*u3.get(): 20
Hi! from myEmbeddedWorld!

In the next article, we’ll dive into the standard library’s shared pointer.
See you!

The next article: Smart Pointer (3/6): std::shared_ptr