add shared_array

simpler version of epics::pvData::shared_vector<T>
w/ offset+capacity tracking, or value conversion.
Updated to use c++11 features (like std::type_index).
This commit is contained in:
Michael Davidsaver
2019-11-26 11:15:51 -08:00
parent 6292776629
commit 4c60d72f9c
4 changed files with 610 additions and 0 deletions
+443
View File
@@ -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 <cstddef>
#include <cstdint>
#include <memory>
#include <type_traits>
#include <algorithm>
#include <ostream>
#include <pvxs/version.h>
namespace pvxs {
class Value;
template<typename E, class Enable = void> class shared_array;
namespace detail {
template<typename T, typename Enable=void>
struct sizeofx {
static inline size_t op() { return sizeof(T); }
};
template<typename T>
struct sizeofx<T, typename std::enable_if<std::is_void<T>{}>::type> {
static inline size_t op() { return 1u; } // treat void* as pointer to bytes
};
template<typename E>
struct sa_default_delete {
void operator()(E* e) const { delete[] e; }
};
template<typename E>
struct sa_base {
protected:
template<typename E1> friend struct sa_base;
std::shared_ptr<E> _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<T>, size_t)
// shared_array(shared_ptr<T>, 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<typename A>
sa_base(A* a, size_t len)
:_data(a, sa_default_delete<E>()),_size(len)
{}
// use existing alloc w/ custom deletor
template<typename B>
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<E>& a, size_t len)
:_data(a),_size(len)
{}
// alias existing shared_ptr
template<typename A>
sa_base(const std::shared_ptr<A>& 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<E>& dataPtr() const { return _data; }
};
} // namespace detail
template<typename E, class Enable>
class shared_array : public detail::sa_base<E> {
static_assert (!std::is_void<E>::value, "non-void specialization");
template<typename E1, class Enable1> friend class shared_array;
typedef detail::sa_base<E> base_t;
typedef typename std::remove_const<E>::type _E_non_const;
public:
typedef E value_type;
typedef E& reference;
typedef typename std::add_const<E>::type& const_reference;
typedef E* pointer;
typedef typename std::add_const<E>::type* const_pointer;
typedef E* iterator;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef typename std::add_const<E>::type* const_iterator;
typedef std::reverse_iterator<const_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<typename A>
shared_array(std::initializer_list<A> 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<typename V>
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<typename B>
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<E>& a, size_t len)
:base_t(a, len)
{}
// alias existing shared_array
template<typename A>
shared_array(const std::shared_ptr<A>& 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<size()
//! Use sa.data() instead of &sa[0]
inline reference operator[](size_t i) const noexcept {return this->_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<typename T>
struct CaptureCode;
#define CASE(TYPE, CODE) \
template<> struct CaptureCode<TYPE> { 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<typename E>
class shared_array<E, typename std::enable_if<std::is_void<E>{}>::type >
: public detail::sa_base<E>
{
static_assert (std::is_void<E>::value, "void specialization");
template<typename E1, class Enable1> friend class shared_array;
typedef detail::sa_base<E> base_t;
typedef typename std::remove_const<E>::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<typename std::remove_cv<E>::type>::code)
{}
//! use existing alloc w/ custom deletor
template<typename B>
shared_array(E* a, B b, size_t len)
:base_t(a, b, len)
,_type(detail::CaptureCode<typename std::remove_cv<E>::type>::code)
{}
//! build around existing shared_ptr and length
shared_array(const std::shared_ptr<E>& a, size_t len)
:base_t(a, len)
,_type(detail::CaptureCode<typename std::remove_cv<E>::type>::code)
{}
//! alias existing shared_ptr and length
template<typename A>
shared_array(const std::shared_ptr<A>& a, E* b, size_t len)
:base_t(a, b, len)
,_type(detail::CaptureCode<typename std::remove_cv<A>::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 <typename SRC>
static inline
shared_array<typename std::add_const<typename SRC::value_type>::type>
freeze(SRC&& src)
{
typedef typename SRC::value_type FROM;
typedef typename std::add_const<FROM>::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<TO> 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<typename TO, typename FROM,
typename std::enable_if< std::is_const<TO>{} == std::is_const<FROM>{}, int >::type=0>
static inline
shared_array<TO>
shared_array_static_cast(const shared_array<FROM>& src)
{
size_t newsize = src.size()*detail::sizeofx<FROM>::op()/detail::sizeofx<TO>::op();
return shared_array<TO>(src.dataPtr(), static_cast<TO*>(src.data()), newsize);
}
template<typename E, typename std::enable_if<!std::is_void<E>{}, int>::type =0>
std::ostream& operator<<(std::ostream& strm, const shared_array<E>& arr)
{
strm<<'{'<<arr.size()<<"}[";
for(size_t i=0; i<arr.size(); i++) {
if(i>10) {
strm<<"...";
break;
}
strm<<arr[i];
if(i+1<arr.size())
strm<<", ";
}
strm<<']';
return strm;
}
PVXS_API
std::ostream& operator<<(std::ostream& strm, const shared_array<const void>& arr);
PVXS_API
std::ostream& operator<<(std::ostream& strm, const shared_array<void>& arr);
} // namespace pvxs
#endif // PVXS_SHAREDVECTOR_H
+21
View File
@@ -13,6 +13,7 @@
*/
#include <sstream>
#include <functional>
#include <pvxs/version.h>
#include <pvxs/util.h>
@@ -41,6 +42,11 @@ public:
explicit operator bool() const { return result==Pass; }
testCase& setPass(bool v) {
result = v ? Pass : Fail;
return *this;
}
template<typename T>
inline testCase& operator<<(const T& v) {
msg<<v;
@@ -99,6 +105,21 @@ testCase testNotEq(const char *sLHS, const LHS& lhs, const char *sRHS, const RHS
} // namespace detail
template<class Exception, typename FN>
testCase testThrows(FN fn)
{
testCase ret(false);
try {
fn();
ret<<"Unexpected success - ";
}catch(Exception& e){
ret.setPass(true)<<"Expected exception \""<<e.what()<<"\" - ";
}catch(std::exception& e){
ret<<"Unexpected exception "<<typeid(e).name()<<" \""<<e.what()<<"\" - ";
}
return ret;
}
} // namespace pvxs
#define testEq(LHS, RHS) ::pvxs::detail::testEq(#LHS, LHS, #RHS, RHS)
+4
View File
@@ -22,6 +22,10 @@ TESTPROD += testudp
testudp_SRCS += testudp.cpp
TESTS += testudp
TESTPROD += testshared
testshared_SRCS += testshared.cpp
TESTS += testshared
TESTPROD += dummyserv
dummyserv_SRCS += dummyserv.cpp
# not a unittest
+142
View File
@@ -0,0 +1,142 @@
/**
* 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.
*/
#include <typeinfo>
#include <pvxs/sharedArray.h>
#include <pvxs/unittest.h>
#include <epicsUnitTest.h>
#include <testMain.h>
namespace {
using namespace pvxs;
template<typename E>
void testEmpty()
{
testDiag("%s", __func__);
shared_array<E> v;
testOk1(v.unique());
testOk1(v.empty());
testEq(v.size(), 0u);
}
template<typename I>
void testInt()
{
testDiag("%s w/ %s", __func__, typeid(I).name());
shared_array<I> X(2, 5);
testOk1(X.unique());
testOk1(!X.empty());
if(testEq(X.size(), 2u)) {
testEq(X[0], 5);
testEq(X[1], 5);
}
shared_array<I> 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<I> 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<I> Q(Y);
testOk1(Y.unique());
testOk1(Q.unique());
testEq(Y.size(), 0u);
testEq(Q.size(), 0u);
}
template<typename Void, typename I>
void testVoid()
{
testDiag("%s", __func__);
shared_array<I> X(2);
shared_array<Void> Y(shared_array_static_cast<Void>(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<uint32_t> X(2, 5);
shared_array<const uint32_t> 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<uint32_t> X(2, 5), Z(X);
testOk1(!X.unique());
testThrows<std::logic_error>([&X]() {
shared_array<const uint32_t> Y(freeze(std::move(X)));
})<<"Attempt to freeze() non-unique";
}
void testComplex()
{
testDiag("%s", __func__);
shared_array<std::unique_ptr<uint32_t>> X(2, nullptr);
X[0] = decltype (X)::value_type{new uint32_t(4u)};
testEq(*X[0], 4u);
}
} // namespace
MAIN(testshared)
{
testPlan(81);
testEmpty<void>();
testEmpty<const void>();
testEmpty<int32_t>();
testEmpty<const int32_t>();
testInt<int32_t>();
testInt<const int32_t>();
testVoid<void, uint32_t>();
testVoid<const void, const uint32_t>();
testFreeze();
testFreezeError();
testComplex();
return testDone();
}