Implementing std::unique_ptr from Scratch
June 17, 2026 · Luciano Muratore
Unique Ownership
A Unique_ptr is a Smart Pointer with the peculiarity that it assigns unique ownership over a managed object.
The present article shows an implementation of unique_ptr, showing the representation of the move constructor, the move assignment, the destructor and also the constructor.
The fact that this pointer is the only one that owns a managed object means that it is not possible to do pointer copy. Having said that, the copy constructor and the copy assignment need to be deleted.
//Copy Constructor Deleted
unique_ptr(const unique_ptr &)=delete;
//Copy Assignment Deleted
unique_ptr &operator=(const unique_ptr &)=delete;
Transfer of Ownership
As said before, since there is only one smart pointer with ownership over the X managed object, then we need to define a move constructor and a move assignment so that a source raw pointer passes its ownership into a other raw pointer. Before going to them, we need to go through release and reset methods.
//Release Method
T *release() noexcept {return std::exchange(m_ptr,nullptr);}
std::exchange assigns the new value (nullptr) to m_ptr, and it returns the previous value of m_ptr.
//Reset Method
void reset(T *ptr = nullptr) noexcept {
T *old = std::exchange(m_ptr, ptr);
if (old) {
delete old;
}
}
reset() is a safety method because it guarantees that the old source raw pointer is deleted.
Now, let’s move into the Move Constructor and the Move Assignment.
//move constructor
unique_ptr(unique_ptr &&other) noexcept : m_ptr{other.release()} {}
//move assignment
unique_ptr &operator=(unique_ptr &&other) noexcept {
if (this != &other) {
reset(other.release());
}
return *this;
}
if(this != & other) is a self-assignment guard. It ensures that you accidentally write p1=std::move(p1),
you don’t accidentally delete your own pointer before trying to assign it.
Destructor
Since the unique_ptr is a smart pointer, it needs to be deleted via the destructor
//Destructor
~unique_ptr() noexcept {
if (m_ptr) {
delete m_ptr;
}
}
Code Implementation
#include <iostream>
template<class T>
class unique_ptr{
private:
T* m_ptr;
public:
//Constructor
unique_ptr() noexcept : unique_ptr{nullptr} {}
explicit unique_ptr(T* ptr) noexcept : m_ptr{ptr} {}
//Copy Constructor Deleted
unique_ptr(const unique_ptr &)=delete;
//Copy Assignment Deleted
unique_ptr &operator=(const unique_ptr &)=delete;
//move constructor
unique_ptr(unique_ptr &&other) noexcept : m_ptr{other.release()} {}
//move assignment
unique_ptr &operator=(unique_ptr &&other) noexcept {
if (this != &other) {
reset(other.release());
}
return *this;
}
//Reset Method
void reset(T *ptr = nullptr) noexcept {
T *old = std::exchange(m_ptr, ptr);
if (old) {
delete old;
}
}
//Get Method
T *get() const noexcept { return m_ptr; }
//Pointer Operators
T *operator->() const noexcept { return m_ptr; }
T &operator*() const noexcept { return *m_ptr; }
//Destructor
~unique_ptr() noexcept {
if (m_ptr) {
delete m_ptr;
}
}
};
template <class T, class... Args>
unique_ptr<T> make_unique(Args &&...args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}