/* Reference-counted smart pointer class

   Copyright (C) 2016-2022 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#ifndef COMMON_GDB_REF_PTR_H
#define COMMON_GDB_REF_PTR_H

#include <cstddef>

namespace gdb
{

/* An instance of this class either holds a reference to a
   reference-counted object or is "NULL".  Reference counting is
   handled externally by a policy class.  If the object holds a
   reference, then when the object is destroyed, the reference is
   decref'd.

   Normally an instance is constructed using a pointer.  This sort of
   initialization lets this class manage the lifetime of that
   reference.

   Assignment and copy construction will make a new reference as
   appropriate.  Assignment from a plain pointer is disallowed to
   avoid confusion about whether this acquires a new reference;
   instead use the "reset" method -- which, like the pointer
   constructor, transfers ownership.

   The policy class must provide two static methods:
   void incref (T *);
   void decref (T *);
*/
template<typename T, typename Policy>
class ref_ptr
{
 public:

  /* Create a new NULL instance.  */
  ref_ptr ()
    : m_obj (NULL)
  {
  }

  /* Create a new NULL instance.  Note that this is not explicit.  */
  ref_ptr (const std::nullptr_t)
    : m_obj (NULL)
  {
  }

  /* Create a new instance.  OBJ is a reference, management of which
     is now transferred to this class.  */
  explicit ref_ptr (T *obj)
    : m_obj (obj)
  {
  }

  /* Copy another instance.  */
  ref_ptr (const ref_ptr &other)
    : m_obj (other.m_obj)
  {
    if (m_obj != NULL)
      Policy::incref (m_obj);
  }

  /* Transfer ownership from OTHER.  */
  ref_ptr (ref_ptr &&other) noexcept
    : m_obj (other.m_obj)
  {
    other.m_obj = NULL;
  }

  /* Destroy this instance.  */
  ~ref_ptr ()
  {
    if (m_obj != NULL)
      Policy::decref (m_obj);
  }

  /* Copy another instance.  */
  ref_ptr &operator= (const ref_ptr &other)
  {
    /* Do nothing on self-assignment.  */
    if (this != &other)
      {
	reset (other.m_obj);
	if (m_obj != NULL)
	  Policy::incref (m_obj);
      }
    return *this;
  }

  /* Transfer ownership from OTHER.  */
  ref_ptr &operator= (ref_ptr &&other)
  {
    /* Do nothing on self-assignment.  */
    if (this != &other)
      {
	reset (other.m_obj);
	other.m_obj = NULL;
      }
    return *this;
  }

  /* Change this instance's referent.  OBJ is a reference, management
     of which is now transferred to this class.  */
  void reset (T *obj)
  {
    if (m_obj != NULL)
      Policy::decref (m_obj);
    m_obj = obj;
  }

  /* Return this instance's referent without changing the state of
     this class.  */
  T *get () const
  {
    return m_obj;
  }

  /* Return this instance's referent, and stop managing this
     reference.  The caller is now responsible for the ownership of
     the reference.  */
  ATTRIBUTE_UNUSED_RESULT T *release ()
  {
    T *result = m_obj;

    m_obj = NULL;
    return result;
  }

  /* Let users refer to members of the underlying pointer.  */
  T *operator-> () const
  {
    return m_obj;
  }

  /* Acquire a new reference and return a ref_ptr that owns it.  */
  static ref_ptr<T, Policy> new_reference (T *obj)
  {
    Policy::incref (obj);
    return ref_ptr<T, Policy> (obj);
  }

 private:

  T *m_obj;
};

template<typename T, typename Policy>
inline bool operator== (const ref_ptr<T, Policy> &lhs,
			const ref_ptr<T, Policy> &rhs)
{
  return lhs.get () == rhs.get ();
}

template<typename T, typename Policy>
inline bool operator== (const ref_ptr<T, Policy> &lhs, const T *rhs)
{
  return lhs.get () == rhs;
}

template<typename T, typename Policy>
inline bool operator== (const ref_ptr<T, Policy> &lhs, const std::nullptr_t)
{
  return lhs.get () == nullptr;
}

template<typename T, typename Policy>
inline bool operator== (const T *lhs, const ref_ptr<T, Policy> &rhs)
{
  return lhs == rhs.get ();
}

template<typename T, typename Policy>
inline bool operator== (const std::nullptr_t, const ref_ptr<T, Policy> &rhs)
{
  return nullptr == rhs.get ();
}

template<typename T, typename Policy>
inline bool operator!= (const ref_ptr<T, Policy> &lhs,
			const ref_ptr<T, Policy> &rhs)
{
  return lhs.get () != rhs.get ();
}

template<typename T, typename Policy>
inline bool operator!= (const ref_ptr<T, Policy> &lhs, const T *rhs)
{
  return lhs.get () != rhs;
}

template<typename T, typename Policy>
inline bool operator!= (const ref_ptr<T, Policy> &lhs, const std::nullptr_t)
{
  return lhs.get () != nullptr;
}

template<typename T, typename Policy>
inline bool operator!= (const T *lhs, const ref_ptr<T, Policy> &rhs)
{
  return lhs != rhs.get ();
}

template<typename T, typename Policy>
inline bool operator!= (const std::nullptr_t, const ref_ptr<T, Policy> &rhs)
{
  return nullptr != rhs.get ();
}

}

#endif /* COMMON_GDB_REF_PTR_H */
