Implementation of unique_ptr in c++98

21 February 2023

Introduced in c++11, unique_ptr is a smart pointer, I already have a post about what are smart pointers, in that post I sorted pointers by order of usefulness, being unique_ptr the most useful followed by raw pointers.

We have raw pointers in c++98, and I don't need shared_ptrs, they are not that useful compared to other pointers, and I can make a workaround for those, but unique_ptrs are really useful, more than raw pointers, so I decided to implement them in c++98, so I can use unique_ptrs in my c++98 projects.

Note: I know we have auto_ptr in c++98, but unique_ptr has more features, features that I thing are useful and worth implementing, like the deleter function template parameter, the array template specialization, and some methods.

Contents

Implementation

Based on GNU c++11 unique_ptr, I cleaned all the c++11 syntax, removed c++11 stuff like the move operators, and using the assing operators instead (which are deleted in the c++11 implementation of course), and I know that's no good, but it's the c++98 way.

This class also depends on my nullptr implementation in c++98, but with some changes, I declared the class type nullptr_t inside a namespace nstd (No Standard), same namespace I used for unique_ptr, so they are not in the global scope, just like std::nullptr_t and std::unique_ptr<> are in the std scope, nstd::nullptr_t and nstd::unique_ptr<> are on their own scope.

But -of course- nullptr instance of nullptr_t is on the global scope, so you can use it just be typing nullptr, the same way you would use the c++11 keyword nullptr.

unique_ptr

unique_ptr.h

#ifndef NSTD_UNIQUEPTR_H_
#define NSTD_UNIQUEPTR_H_

#include <algorithm> // std::swap

#include "nullptr_t.h"

namespace nstd {

template <typename Tp>
struct default_delete {
  default_delete() {}
  ~default_delete() {}

  void operator()(Tp* ptr) const {
    //assert(sizeof(Tp) > 0); // this should be a static_assert in c++11
    delete ptr;
  }
};

template <typename Tp>
struct default_delete<Tp[]> {
  default_delete() {}

  void operator()(Tp* ptr) const {
    //assert(sizeof(Tp) > 0); // this should be a static_assert in c++11
    delete[] ptr;
  }
};

template <typename Tp, typename Dp = default_delete<Tp> >
class unique_ptr {
  public:
    typedef Tp* pointer;
    typedef Tp element_type;
    typedef Dp deleter_type;

    // constructors
    unique_ptr() {}

    explicit unique_ptr(pointer p)
        : p_(p), d_(deleter_type()) {}

    unique_ptr(pointer p, deleter_type d)
        : p_(p), d_(d) {}

    unique_ptr(nullptr_t)
        : p_(nullptr), d_(nullptr) {}

    // move constructors
    // we are in c++98 so they are assign, but will behave like move ones
    unique_ptr(unique_ptr& u) : p_(u.release(), d_(u.get_deleter())) {}

    // destructor
    ~unique_ptr() {
      reset();
    }

    // movement
    // we are in c++98 so they are assigment, but will behave like move ones
    unique_ptr& operator=(unique_ptr& u) {
      reset(u.release());
      get_deleter() = u.get_deleter();
      return *this;
    }

    unique_ptr& operator=(nullptr_t) {
      reset();
      return *this;
    }

    // observers
    //typename element_type::type& operator*() const {
    element_type& operator*() const {
      return *get();
    }

    pointer operator->() const {
      return get();
    }

    pointer get() const {
      return p_;
    }

    deleter_type& get_deleter() {
      return d_;
    }
    const deleter_type& get_deleter() const {
      return d_;
    }

    operator bool() const {
      return get() == pointer() ? false : true;
    }

    // modifiers
    pointer release() {
      pointer p = get();
      p_ = pointer();
      return p;
    }

    void reset(pointer p = pointer()) {
      std::swap(p_, p);
      if (p != pointer()) {
        get_deleter()(p);
      }
    }

    void swap(unique_ptr& u) {
      std::swap(p_, u.p_);
      std::swap(d_, u.d_);
    }
  private:
    pointer p_;
    deleter_type d_;
};

template <typename Tp, typename Dp>
class unique_ptr<Tp[], Dp> {
  public:
    typedef Tp* pointer;
    typedef Tp element_type;
    typedef Dp deleter_type;

    // constructors
    unique_ptr() {}

    explicit unique_ptr(pointer p)
        : p_(p), d_(deleter_type()) {}

    unique_ptr(pointer p, deleter_type d)
        : p_(p), d_(d) {}

    unique_ptr(nullptr_t)
        : p_(nullptr), d_(nullptr) {}

    // move constructors
    // we are in c++98 so they are assign, but will behave like move ones
    unique_ptr(unique_ptr& u) : p_(u.release(), d_(u.get_deleter())) {}

    // destructor
    ~unique_ptr() {
      reset();
    }

    // movement
    // we are in c++98 so they are assigment, but will behave like move ones
    unique_ptr& operator=(unique_ptr& u) {
      reset(u.release());
      get_deleter() = u.get_deleter();
      return *this;
    }

    unique_ptr& operator=(nullptr_t) {
      reset();
      return *this;
    }

    // observers
    //typename element_type::type& operator[](size_t i) const {
    element_type& operator[](size_t i) const {
      return get()[i];
    }

    pointer get() const {
      return p_;
    }

    deleter_type& get_deleter() {
      return d_;
    }
    const deleter_type& get_deleter() const {
      return d_;
    }

    operator bool() const {
      return get() == pointer() ? false : true;
    }

    // modifiers
    pointer release() {
      pointer p = get();
      p_ = pointer();
      return p;
    }

    void reset(pointer p = pointer()) {
      std::swap(p_, p);
      if (p != nullptr) {
        get_deleter()(p);
      }
    }
    void reset(nullptr_t) {
      pointer p = get();
      p_ = pointer();
      if (p != nullptr) {
        get_deleter()(p);
      }
    }

    void swap(unique_ptr& u) {
      std::swap(p_, u.p_);
      std::swap(d_, u.d_);
    }
  private:
    pointer p_;
    deleter_type d_;
};

template <typename Tp, typename Dp>
inline void swap(unique_ptr<Tp, Dp>& x, unique_ptr<Tp, Dp>& y) {
  x.swap(y);
}

template <typename Tp, typename Dp, typename Up, typename Ep>
inline bool operator==(const unique_ptr<Tp, Dp>& x, const unique_ptr<Up, Ep>& y) {
  return x.get() == y.get();
}
template <typename Tp, typename Dp>
inline bool operator==(const unique_ptr<Tp, Dp>& x, nullptr_t) {
  return x.get() == nullptr;
}
template <typename Tp, typename Dp>
inline bool operator==(nullptr_t, const unique_ptr<Tp, Dp>& y) {
  return nullptr == y.get();
}

template <typename Tp, typename Dp, typename Up, typename Ep>
inline bool operator!=(const unique_ptr<Tp, Dp>& x, const unique_ptr<Up, Ep>& y) {
  return !(x.get() == y.get());
}
template <typename Tp, typename Dp>
inline bool operator!=(const unique_ptr<Tp, Dp>& x, nullptr_t) {
  return x.get() != nullptr;
}
template <typename Tp, typename Dp>
inline bool operator!=(nullptr_t, const unique_ptr<Tp, Dp>& y) {
  return nullptr != y.get();
}

template <typename Tp, typename Dp, typename Up, typename Ep>
inline bool operator<(const unique_ptr<Tp, Dp>& x, const unique_ptr<Up, Ep>& y) {
  return x.get() < y.get();
}
template <typename Tp, typename Dp, typename Up, typename Ep>
inline bool operator<=(const unique_ptr<Tp, Dp>& x, const unique_ptr<Up, Ep>& y) {
  return !(y.get() < x.get());
}
template <typename Tp, typename Dp, typename Up, typename Ep>
inline bool operator>(const unique_ptr<Tp, Dp>& x, const unique_ptr<Up, Ep>& y) {
  return y.get() < x.get();
}
template <typename Tp, typename Dp, typename Up, typename Ep>
inline bool operator>=(const unique_ptr<Tp, Dp>& x, const unique_ptr<Up, Ep>& y) {
  return !(x.get() < y.get());
}

} // namespace nstd

#endif // NSTD_UNIQUEPTR_H_

nullptr_t

nullptr_t.h

#ifndef NSTD_NULLPTRT_H_
#define NSTD_NULLPTRT_H_

namespace nstd {

class nullptr_t {
  public:
    template <class T>
    operator T*() const {
      return 0;
    }
    template <class C, class T>
    operator T C::*() const {
      return 0;
    }
  private:
    void operator&() const;
};

} // namespace nstd

const nstd::nullptr_t nullptr = {};

#endif // NSTD_NULLPTRT_H_

Sources