From 4c60d72f9cbb0d3b520368fb4609eff3907000f2 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 26 Nov 2019 11:15:51 -0800 Subject: [PATCH] add shared_array simpler version of epics::pvData::shared_vector w/ offset+capacity tracking, or value conversion. Updated to use c++11 features (like std::type_index). --- src/pvxs/sharedArray.h | 443 +++++++++++++++++++++++++++++++++++++++++ src/pvxs/unittest.h | 21 ++ test/Makefile | 4 + test/testshared.cpp | 142 +++++++++++++ 4 files changed, 610 insertions(+) create mode 100644 src/pvxs/sharedArray.h create mode 100644 test/testshared.cpp diff --git a/src/pvxs/sharedArray.h b/src/pvxs/sharedArray.h new file mode 100644 index 0000000..82a8fa3 --- /dev/null +++ b/src/pvxs/sharedArray.h @@ -0,0 +1,443 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvxs is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ +#ifndef PVXS_SHAREDVECTOR_H +#define PVXS_SHAREDVECTOR_H + +#include +#include +#include +#include +#include +#include + +#include + +namespace pvxs { + +class Value; + +template class shared_array; + +namespace detail { + +template +struct sizeofx { + static inline size_t op() { return sizeof(T); } +}; +template +struct sizeofx{}>::type> { + static inline size_t op() { return 1u; } // treat void* as pointer to bytes +}; + +template +struct sa_default_delete { + void operator()(E* e) const { delete[] e; } +}; + +template +struct sa_base { +protected: + template friend struct sa_base; + + std::shared_ptr _data; + size_t _size; +public: + + // shared_array() + // shared_array(const shared_array&) + // shared_array(shared_array&&) + // shared_array(size_t, T) + // shared_array(T*, size_t) + // shared_array(T*, d, size_t) + // shared_array(shared_ptr, size_t) + // shared_array(shared_ptr, T*, size_t) + + //! empty + constexpr sa_base() :_size(0u) {} + + // copyable + sa_base(const sa_base&) = default; + // movable + inline sa_base(sa_base&& o) noexcept + :_data(std::move(o._data)), _size(o._size) + { + o._size = 0; + } + sa_base& operator=(const sa_base&) =default; + inline sa_base& operator=(sa_base&& o) noexcept + { + _data = std::move(o._data); + _size = o._size; + o._size = 0; + return *this; + } + + // use existing alloc with delete[] + template + sa_base(A* a, size_t len) + :_data(a, sa_default_delete()),_size(len) + {} + + // use existing alloc w/ custom deletor + template + sa_base(E* a, B b, size_t len) + :_data(a, b),_size(len) + {} + + // build around existing shared_ptr + sa_base(const std::shared_ptr& a, size_t len) + :_data(a),_size(len) + {} + + // alias existing shared_ptr + template + sa_base(const std::shared_ptr& a, E* b, size_t len) + :_data(a, b),_size(len) + {} + + void clear() noexcept { + _data.reset(); + _size = 0; + } + + void swap(sa_base& o) noexcept { + std::swap(_data, o._data); + std::swap(_size, o._data); + } + + inline size_t size() const { return _size; } + inline bool empty() const noexcept { return _size==0; } + + inline bool unique() const noexcept { return !_data || _data.use_count()<=1; } + + E* data() const noexcept { return _data.get(); } + + const std::shared_ptr& dataPtr() const { return _data; } +}; + +} // namespace detail + +template +class shared_array : public detail::sa_base { + static_assert (!std::is_void::value, "non-void specialization"); + + template friend class shared_array; + + typedef detail::sa_base base_t; + typedef typename std::remove_const::type _E_non_const; +public: + typedef E value_type; + typedef E& reference; + typedef typename std::add_const::type& const_reference; + typedef E* pointer; + typedef typename std::add_const::type* const_pointer; + typedef E* iterator; + typedef std::reverse_iterator reverse_iterator; + typedef typename std::add_const::type* const_iterator; + typedef std::reverse_iterator const_reverse_iterator; + typedef std::ptrdiff_t difference_type; + typedef size_t size_type; + + typedef E element_type; + + constexpr shared_array() noexcept :base_t() {} + + template + shared_array(std::initializer_list L) + :base_t(new _E_non_const[L.size()], L.size()) + { + _E_non_const *raw = const_cast<_E_non_const*>(this->data()); + std::copy(L.begin(), L.end(), raw); + } + + //! @brief Allocate (with new[]) a new vector of size c + explicit shared_array(size_t c) + :base_t(new _E_non_const[c], c) + {} + + //! @brief Allocate (with new[]) a new vector of size c and fill with value e + template + shared_array(size_t c, V e) + :base_t(new _E_non_const[c], c) + { + std::fill_n((_E_non_const*)this->_data.get(), this->_size, e); + } + + // use existing alloc with delete[] + shared_array(E* a, size_t len) + :base_t(a, len) + {} + + // use existing alloc w/ custom deletor + template + shared_array(E* a, B b, size_t len) + :base_t(a, b, len) + {} + + // build around existing shared_ptr + shared_array(const std::shared_ptr& a, size_t len) + :base_t(a, len) + {} + + // alias existing shared_array + template + shared_array(const std::shared_ptr& a, E* b, size_t len) + :base_t(a, b, len) + {} + + size_t max_size() const noexcept {return ((size_t)-1)/sizeof(E);} + + inline void reserve(size_t i) {} + + void resize(size_t i) { + if(!this->unique() || i!=this->_size) { + shared_array o(i); + std::copy_n(this->begin(), std::min(this->size(), i), o.begin()); + this->swap(o); + } + } + + inline void make_unique() { + this->resize(this->size()); + } + +private: + /* Hack alert. + * For reasons of simplicity and efficiency, we want to use raw pointers for iteration. + * However, shared_ptr::get() isn't defined when !_data, although practically it gives NULL. + * Unfortunately, many of the MSVC (<= VS 2010) STL methods assert() that iterators are never NULL. + * So we fudge here by abusing 'this' so that our iterators are always !NULL. + */ + inline E* base_ptr() const { +#if defined(_MSC_VER) && _MSC_VER<=1600 + return this->_size ? this->_data.get() : (E*)(this-1); +#else + return this->_data.get(); +#endif + } +public: + // STL iterators + + inline iterator begin() const noexcept{return this->base_ptr();} + inline const_iterator cbegin() const noexcept{return begin();} + + inline iterator end() const noexcept{return this->base_ptr()+this->_size;} + inline const_iterator cend() const noexcept{return end();} + + inline reverse_iterator rbegin() const noexcept{return reverse_iterator(end());} + inline const_reverse_iterator crbegin() const noexcept{return rbegin();} + + inline reverse_iterator rend() const noexcept{return reverse_iterator(begin());} + inline const_reverse_iterator crend() const noexcept{return rend();} + + inline reference front() const noexcept{return (*this)[0];} + inline reference back() const noexcept{return (*this)[this->m_count-1];} + + //! @brief Member access + //! @pre !empty() && i_data.get()[i];} + + //! @brief Member access + //! @throws std::out_of_range if empty() || i>=size(). + reference at(size_t i) const + { + if(i>this->_size) + throw std::out_of_range("Index out of bounds"); + return (*this)[i]; + } +}; + +enum class ArrayType : uint8_t { + Null = 0xff, + Bool = 0x08, + Int8 = 0x28, + Int16 = 0x29, + Int32 = 0x2a, + Int64 = 0x2b, + UInt8 = 0x2c, + UInt16= 0x2d, + UInt32= 0x2e, + UInt64= 0x2f, + Float = 0x4a, + Double= 0x4b, + String= 0x68, + Value = 0x88, // also used for 0x89 and 0x8a +}; + +PVXS_API +std::ostream& operator<<(std::ostream& strm, ArrayType code); + +namespace detail { +template +struct CaptureCode; + +#define CASE(TYPE, CODE) \ +template<> struct CaptureCode { static constexpr ArrayType code{ArrayType::CODE}; } +CASE(bool, Bool); +CASE(int8_t, Int8); +CASE(int16_t, Int16); +CASE(int32_t, Int32); +CASE(int64_t, Int64); +CASE(uint8_t, UInt8); +CASE(uint16_t, UInt16); +CASE(uint32_t, UInt32); +CASE(uint64_t, UInt64); +CASE(float, Float); +CASE(double, Double); +CASE(std::string, String); +CASE(Value, Value); +#undef CASE + +} // namespace detail + + +template +class shared_array{}>::type > + : public detail::sa_base +{ + static_assert (std::is_void::value, "void specialization"); + + template friend class shared_array; + + typedef detail::sa_base base_t; + typedef typename std::remove_const::type _E_non_const; + + ArrayType _type; +public: + typedef E value_type; + typedef E* pointer; + typedef std::ptrdiff_t difference_type; + typedef size_t size_type; + + //! empty array, untyped + constexpr shared_array() noexcept :base_t(), _type(ArrayType::Null) {} + //! empty array, typed + constexpr explicit shared_array(ArrayType code) noexcept :base_t(), _type(code) {} + //! copy + shared_array(const shared_array& o) = default; + //! move + inline shared_array(shared_array&& o) noexcept + :base_t(std::move(o)) + ,_type(o._type) + { + o._type = ArrayType::Null; + } + //! assign + shared_array& operator=(const shared_array&) =default; + //! move + inline shared_array& operator=(shared_array&& o) noexcept + { + base_t::operator=(std::move(o)); + _type = o._type; + o._type = ArrayType::Null; + return *this; + } + + //! use existing alloc with delete[] + shared_array(E* a, size_t len) + :base_t(a, len) + ,_type(detail::CaptureCode::type>::code) + {} + + //! use existing alloc w/ custom deletor + template + shared_array(E* a, B b, size_t len) + :base_t(a, b, len) + ,_type(detail::CaptureCode::type>::code) + {} + + //! build around existing shared_ptr and length + shared_array(const std::shared_ptr& a, size_t len) + :base_t(a, len) + ,_type(detail::CaptureCode::type>::code) + {} + + //! alias existing shared_ptr and length + template + shared_array(const std::shared_ptr& a, E* b, size_t len) + :base_t(a, b, len) + ,_type(detail::CaptureCode::type>::code) + {} + + //! clear data and become untyped + void clear() noexcept { + base_t::clear(); + _type = ArrayType::Null; + } + + //! exchange + void swap(shared_array& o) noexcept { + base_t::swap(o); + std::swap(_type, o._type); + } + + size_t max_size() const noexcept{return (size_t)-1;} + + inline ArrayType original_type() const { return _type; } +}; + +// non-const -> const +template +static inline +shared_array::type> +freeze(SRC&& src) +{ + typedef typename SRC::value_type FROM; + typedef typename std::add_const::type TO; + + if(!src.unique()) + throw std::logic_error("Can't freeze non-unique shared_array"); + + // cast data pointer to const + TO* data = src.data(); + + shared_array ret(src.dataPtr(), data, src.size()); + + // c++20 provides a move()-able alternative to the aliasing constructor. + // until this stops being the future, we consume the src ref. and + // inc. + dec. the ref counter... + auto temp(std::move(src)); + return ret; +} + +// change type, while keeping same const +template{} == std::is_const{}, int >::type=0> +static inline +shared_array +shared_array_static_cast(const shared_array& src) +{ + size_t newsize = src.size()*detail::sizeofx::op()/detail::sizeofx::op(); + return shared_array(src.dataPtr(), static_cast(src.data()), newsize); +} + +template{}, int>::type =0> +std::ostream& operator<<(std::ostream& strm, const shared_array& arr) +{ + strm<<'{'<10) { + strm<<"..."; + break; + } + strm<& arr); + +PVXS_API +std::ostream& operator<<(std::ostream& strm, const shared_array& arr); + +} // namespace pvxs + +#endif // PVXS_SHAREDVECTOR_H diff --git a/src/pvxs/unittest.h b/src/pvxs/unittest.h index 202f9a2..1daae32 100644 --- a/src/pvxs/unittest.h +++ b/src/pvxs/unittest.h @@ -13,6 +13,7 @@ */ #include +#include #include #include @@ -41,6 +42,11 @@ public: explicit operator bool() const { return result==Pass; } + testCase& setPass(bool v) { + result = v ? Pass : Fail; + return *this; + } + template inline testCase& operator<<(const T& v) { msg< +testCase testThrows(FN fn) +{ + testCase ret(false); + try { + fn(); + ret<<"Unexpected success - "; + }catch(Exception& e){ + ret.setPass(true)<<"Expected exception \""< + +#include + +#include +#include +#include + +namespace { +using namespace pvxs; + +template +void testEmpty() +{ + testDiag("%s", __func__); + + shared_array v; + + testOk1(v.unique()); + testOk1(v.empty()); + + testEq(v.size(), 0u); +} + +template +void testInt() +{ + testDiag("%s w/ %s", __func__, typeid(I).name()); + + shared_array X(2, 5); + testOk1(X.unique()); + testOk1(!X.empty()); + if(testEq(X.size(), 2u)) { + testEq(X[0], 5); + testEq(X[1], 5); + } + + shared_array Y(X); + testOk1(!X.unique()); + testOk1(!Y.unique()); + testEq(X.size(), Y.size()); + + X.clear(); + testOk1(X.unique()); + testOk1(Y.unique()); + testEq(X.size(), 0u); + testEq(Y.size(), 2u); + + X = std::move(Y); + testOk1(X.unique()); + testOk1(Y.unique()); + testEq(X.size(), 2u); + testEq(Y.size(), 0u); + + shared_array Z(std::move(X)); + testOk1(X.unique()); + testOk1(Y.unique()); + testOk1(Z.unique()); + testEq(X.size(), 0u); + testEq(Y.size(), 0u); + testEq(Z.size(), 2u); + + // copy empty + shared_array Q(Y); + testOk1(Y.unique()); + testOk1(Q.unique()); + testEq(Y.size(), 0u); + testEq(Q.size(), 0u); +} + +template +void testVoid() +{ + testDiag("%s", __func__); + + shared_array X(2); + + shared_array Y(shared_array_static_cast(X)); + testOk1(!X.unique()); + testOk1(!Y.unique()); + testEq(X.size(), 2u); + testEq(Y.size(), 8u); + testEq(Y.original_type(), ArrayType::UInt32); // never const uint32_t +} + +void testFreeze() +{ + testDiag("%s", __func__); + + shared_array X(2, 5); + shared_array Y(freeze(std::move(X))); + testOk1(X.unique()); + testOk1(Y.unique()); + testEq(X.size(), 0u); + testEq(Y.size(), 2u); +} + +void testFreezeError() +{ + testDiag("%s", __func__); + + shared_array X(2, 5), Z(X); + testOk1(!X.unique()); + testThrows([&X]() { + shared_array Y(freeze(std::move(X))); + })<<"Attempt to freeze() non-unique"; +} + +void testComplex() +{ + testDiag("%s", __func__); + + shared_array> X(2, nullptr); + + X[0] = decltype (X)::value_type{new uint32_t(4u)}; + testEq(*X[0], 4u); +} + +} // namespace + +MAIN(testshared) +{ + testPlan(81); + testEmpty(); + testEmpty(); + testEmpty(); + testEmpty(); + testInt(); + testInt(); + testVoid(); + testVoid(); + testFreeze(); + testFreezeError(); + testComplex(); + return testDone(); +}