The fundamental properties of a smart pointer type are simple: it should support operator*, and it should overload the special member functions to preserve its class invariants, whatever those are.
std::unique_ptr<T> supports the same interface as T*, but with the class invariant that, once you construct a unique_ptr pointing to a given heap-allocated object, that object will be freed when the destructor unique_ptr is called. Let's write some code supporting that T* interface:
template<typename T>
class unique_ptr {
T *m_ptr = nullptr;
public:
constexpr unique_ptr() noexcept = default;
constexpr unique_ptr(T *p) noexcept : m_ptr(p) {}
T *get() const noexcept { return m_ptr; }
operator bool() const noexcept { return bool(get()); }
T& operator*() const noexcept...