4
\$\begingroup\$

What might be wrong with this shared pointer? One good point of it might be, that it should handle array types correctly by default (e.g. light_ptr<int[]>(new int[10]).

std::shared_ptr has the advantage of being more portable. For example, emscripten provides an implementation, but light_ptr won't compile, still, it can be ported to emscripten easily. The native implementation may also be more optimized.

#pragma once
#ifndef LIGHTPTR_HPP
# define LIGHTPTR_HPP

#include <cassert>

#include <atomic>

#include <memory>

#include <utility>

#include <type_traits>

namespace detail
{
  using counter_type = ::std::size_t;

  using atomic_type = ::std::atomic<counter_type>;

  template <typename T>
  using deleter_type = void (*)(T*);

  template <typename U>
  struct ref_type
  {
    using type = U&;
  };

  template <>
  struct ref_type<void>
  {
    using type = void;
  };

  template <typename T>
  inline void dec_ref(atomic_type* const counter_ptr,
    T* const ptr, deleter_type<T> const deleter)
  {
    if (counter_ptr && (counter_type(1) ==
      counter_ptr->fetch_sub(counter_type(1), ::std::memory_order_relaxed)))
    {
      delete counter_ptr;

      deleter(ptr);
    }
    // else do nothing
  }

  inline void inc_ref(atomic_type* const counter_ptr)
  {
    assert(counter_ptr);
    counter_ptr->fetch_add(counter_type(1), ::std::memory_order_relaxed);
  }
}

template <typename T>
struct light_ptr
{
  template <typename U, typename V>
  struct deletion_type
  {
    using type = V;
  };

  template <typename U, typename V>
  struct deletion_type<U[], V>
  {
    using type = V[];
  };

  template <typename U, typename V, ::std::size_t N>
  struct deletion_type<U[N], V>
  {
    using type = V[];
  };

  template <typename U>
  struct remove_array
  {
    using type = U;
  };

  template <typename U>
  struct remove_array<U[]>
  {
    using type = U;
  };

  template <typename U, ::std::size_t N>
  struct remove_array<U[N]>
  {
    using type = U;
  };

  using element_type = typename remove_array<T>::type;

  using deleter_type = ::detail::deleter_type<element_type>;

  light_ptr() = default;

  template <typename U>
  explicit light_ptr(U* const p,
    deleter_type const d = default_deleter<U>)
  {
    reset(p, d);
  }

  ~light_ptr() { ::detail::dec_ref(counter_ptr_, ptr_, deleter_); }

  light_ptr(light_ptr const& other) { *this = other; }

  light_ptr(light_ptr&& other) noexcept { *this = ::std::move(other); }

  light_ptr& operator=(light_ptr const& rhs)
  {
    if (*this != rhs)
    {
      ::detail::dec_ref(counter_ptr_, ptr_, deleter_);

      counter_ptr_ = rhs.counter_ptr_;
      ptr_ = rhs.ptr_;

      deleter_ = rhs.deleter_;

      ::detail::inc_ref(counter_ptr_);
    }
    // else do nothing

    return *this;
  }

  light_ptr& operator=(light_ptr&& rhs) noexcept
  {
    if (*this != rhs)
    {
      counter_ptr_ = rhs.counter_ptr_;
      ptr_ = rhs.ptr_;

      deleter_ = rhs.deleter_;

      rhs.counter_ptr_ = nullptr;
      rhs.ptr_ = nullptr;
    }
    // else do nothing

    return *this;
  }

  bool operator<(light_ptr const& rhs) const noexcept
  {
    return get() < rhs.get();
  }

  bool operator==(light_ptr const& rhs) const noexcept
  {
    return counter_ptr_ == rhs.counter_ptr_;
  }

  bool operator!=(light_ptr const& rhs) const noexcept
  {
    return !operator==(rhs);
  }

  bool operator==(::std::nullptr_t const) const noexcept
  {
    return !ptr_;
  }

  bool operator!=(::std::nullptr_t const) const noexcept
  {
    return ptr_;
  }

  explicit operator bool() const noexcept { return ptr_; }

  typename ::detail::ref_type<T>::type
  operator*() const noexcept
  {
    return *static_cast<T*>(static_cast<void*>(ptr_));
  }

  T* operator->() const noexcept
  {
    return static_cast<T*>(static_cast<void*>(ptr_));
  }

  element_type* get() const noexcept { return ptr_; }

  void reset() { reset(nullptr); }

  void reset(::std::nullptr_t const)
  {
    ::detail::dec_ref(counter_ptr_, ptr_, deleter_);

    counter_ptr_ = nullptr;
    ptr_ = nullptr;
  }

  template <typename U>
  void reset(U* const p, deleter_type const d = default_deleter<U>)
  {
    ::detail::dec_ref(counter_ptr_, ptr_, deleter_);

    counter_ptr_ = new ::detail::atomic_type(::detail::counter_type(1));
    ptr_ = p;

    deleter_ = d;
  }

  void swap(light_ptr& other) noexcept
  {
    ::std::swap(counter_ptr_, other.counter_ptr_);
    ::std::swap(ptr_, other.ptr_);
    ::std::swap(deleter_, other.deleter_);
  }

  bool unique() const noexcept
  {
    return ::detail::counter_type(1) == use_count();
  }

  ::detail::counter_type use_count() const noexcept
  {
    return counter_ptr_ ?
      counter_ptr_->load(::std::memory_order_relaxed) :
      ::detail::counter_type{};
  }

  template <typename U>
  static void default_deleter(element_type* const p)
  {
    ::std::default_delete<typename deletion_type<T, U>::type>()(
      static_cast<U*>(p));
  }

private:
  ::detail::atomic_type* counter_ptr_{};

  element_type* ptr_{};

  deleter_type deleter_;
};

template<class T, class ...Args>
inline light_ptr<T> make_light(Args&& ...args)
{
  return light_ptr<T>(new T(::std::forward<Args>(args)...));
}

namespace std
{
  template <typename T>
  struct hash<light_ptr<T> >
  {
    size_t operator()(light_ptr<T> const& l) const noexcept
    {
      return hash<typename light_ptr<T>::element_type*>(l.get());
    }
  };
}

#endif // LIGHTPTR_HPP

Usage:

#include <iostream>

#include <thread>

#include <vector>

#include "lightptr.hpp"

int main()
{
  {
    light_ptr<int> p(new int);

    *p = 10;

    light_ptr<int> s(p);

    p.reset(new int);

    std::cout << s.use_count() << ": " << *s << std::endl;

    std::vector<light_ptr<float> > v(1000000);

    for (auto& r: v)
    {
      r.reset(new float);

      *r = 100;
    }

    auto a(make_light<char[4]>());

    (*a)[2] = 10;
    a.get()[3] = 10;

    ::std::thread a_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});
    ::std::thread b_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});
    ::std::thread c_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});
    ::std::thread d_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});
    ::std::thread e_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});
    ::std::thread f_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});
    ::std::thread g_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});
    ::std::thread h_([&p]{light_ptr<int> a(p); std::cout << p.use_count() << std::endl;});

    a_.join(); b_.join(); c_.join(); d_.join();
    e_.join(); f_.join(); g_.join(); h_.join();
  }

  struct A
  {
    ~A() { std::cout << "deleted" << std::endl; }
  };

  light_ptr<void> a(new A);

  light_ptr<A[]> b(new A[1]);

  return 0;
}
\$\endgroup\$
6
  • 2
    \$\begingroup\$ Writing your own shared pointer is a bad idea. It is a lot harder than you think to just get it working correctly. Optimizing is a step on top of that. But making it work should be your priority. PS. The standard one does not use any compiler specific tricks. \$\endgroup\$ Commented Aug 29, 2013 at 22:33
  • \$\begingroup\$ However, I can point out one thing (so far): you're using two different header guards. I'd remove the #pragma once while keeping the other. \$\endgroup\$
    – Jamal
    Commented Aug 30, 2013 at 8:13
  • \$\begingroup\$ @Jamal That's a backup for compilers that don't support the #pragma once. \$\endgroup\$ Commented Aug 30, 2013 at 8:19
  • \$\begingroup\$ @user1095108: In that case you are stacking them wrong. Put the pragma inside the other header-guard. \$\endgroup\$ Commented May 13, 2014 at 21:31
  • \$\begingroup\$ @Deduplicator I've read somewhere that #pragma once is faster of the two header guards. That's why I put it first, since otherwise I would defeat the purpose why I use both header guards. There seems to be nothing wrong, if one uses both. \$\endgroup\$ Commented May 13, 2014 at 21:36

1 Answer 1

4
\$\begingroup\$

The move assignment operator is broken:

light_ptr& operator=(light_ptr&& rhs) noexcept

You over write the current members with the values from the rhs, but do not decrement the reference counter before overwriting. It might be easier to swap *this with rhs.

Don't include types that are not being used:

  template <typename U, typename V, ::std::size_t N>
  struct deletion_type<U[N], V>
  {
    using type = V[];
  };

  template <typename U, ::std::size_t N>
  struct remove_array<U[N]>
  {
    using type = U;
  };

Casting to void*. Once this is done the only valid operation is to cast back to the original type. Annything else is undefined.

 return static_cast<T*>(static_cast<void*>(ptr_));

In this case element_type* is the same as T*. But the code is written in such a way that any changes by a maintainer can easily result in this being not true. Also I don;t actually see the need for a cast.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.