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;
}
#pragma once
while keeping the other. \$\endgroup\$#pragma once
. \$\endgroup\$#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\$