diff --git a/pvDataApp/Makefile b/pvDataApp/Makefile index bd628a4..cde0b2b 100644 --- a/pvDataApp/Makefile +++ b/pvDataApp/Makefile @@ -26,6 +26,7 @@ INC += sharedPtr.h INC += localStaticLock.h INC += typeCast.h INC += printer.h +INC += sharedVector.h LIBSRCS += byteBuffer.cpp LIBSRCS += bitSet.cpp diff --git a/pvDataApp/misc/epicsException.h b/pvDataApp/misc/epicsException.h index 24025cd..b7bfad6 100644 --- a/pvDataApp/misc/epicsException.h +++ b/pvDataApp/misc/epicsException.h @@ -103,7 +103,7 @@ namespace detail { * * Takes advantage of the requirement that all exception classes * must be copy constructable. Of course this also requires - * the and extra copy be constructed... + * that an extra copy be constructed... */ template class ExceptionMixed : public E, public ExceptionMixin { diff --git a/pvDataApp/misc/sharedVector.h b/pvDataApp/misc/sharedVector.h new file mode 100644 index 0000000..e5098a8 --- /dev/null +++ b/pvDataApp/misc/sharedVector.h @@ -0,0 +1,734 @@ +#ifndef SHAREDVECTOR_H +#define SHAREDVECTOR_H + +#include +#include +#include +#include + +#include "pv/sharedPtr.h" + +namespace epics { namespace pvData { + +template class shared_vector; + +namespace detail { + template + struct default_array_deleter {void operator()(E a){delete[] a;}}; + + // Implicit casts allowed during copy construction of shared_vector + template + struct vector_implicit_cast { + // There is intentionally no implmentation here to cause + // compile errors for illegal casts. + //static std::tr1::shared_ptr cast(const std::tr1::shared_ptr); + }; + // non-const -> const + template + struct vector_implicit_cast { + static std::tr1::shared_ptr cast(const std::tr1::shared_ptr input) + { + return std::tr1::shared_ptr(input); + } + }; + + /* All the parts of shared_vector which + * don't need special handling for E=void + */ + template + class shared_vector_base + { + // allow specialization for all E to be friends + template friend class shared_vector_base; + protected: + std::tr1::shared_ptr m_data; + //! Offset in the data array of first element + size_t m_offset; + //! Number of visible elements between m_offset and end of data + size_t m_count; + //! Total number of elements between m_offset and the end of data + size_t m_total; + + public: + + //! @brief Empty vector (not very interesting) + shared_vector_base() + :m_data(), m_offset(0), m_count(0), m_total(0) + {} + + protected: + // helper for constructors + // Ensure that offset and size are zero when we are constructed with NULL + void _null_input() + { + if(!m_data.get()) { + m_offset = m_total = m_count = 0; + } + } + public: + + template + shared_vector_base(A v, size_t o, size_t c) + :m_data(v, detail::default_array_deleter()) + ,m_offset(o), m_count(c), m_total(c) + {_null_input();} + + template + shared_vector_base(const std::tr1::shared_ptr& d, size_t o, size_t c) + :m_data(d), m_offset(o), m_count(c), m_total(c) + {_null_input();} + + + template + shared_vector_base(A d, B b, size_t o, size_t c) + :m_data(d,b), m_offset(o), m_count(c), m_total(c) + {_null_input();} + + template + shared_vector_base(const shared_vector_base& o) + : m_data(vector_implicit_cast::cast(o.m_data)) + , m_offset(o.m_offset), m_count(o.m_count), m_total(o.m_total) + {_null_input();} + + //! @brief Copy an existing vector + shared_vector_base& operator=(const shared_vector_base& o) + { + if(&o!=this) { + m_data=o.m_data; + m_offset=o.m_offset; + m_count=o.m_count; + m_total=o.m_total; + } + return *this; + } + + //! @brief Swap the contents of this vector with another + void swap(shared_vector_base& o) { + if(&o!=this) { + m_data.swap(o.m_data); + std::swap(m_count, o.m_count); + std::swap(m_offset, o.m_offset); + std::swap(m_total, o.m_total); + } + } + + //! @brief Clear contents. + //! size() becomes 0 + void clear() { + m_data.reset(); + m_offset = m_total = m_count = 0; + } + + //! @brief Data is not shared? + bool unique() const {return !m_data || m_data.unique();} + + + //! @brief Number of elements visible through this vector + size_t size() const{return m_count;} + bool empty() const{return !m_count;} + + size_t max_size() const{return (size_t)-1;} + + + /** @brief Reduce the view of this shared_vector. + * + * Reduce the portion of the underlying buffer which + * is accessible through this shared_vector. + * + * When the requested new offset and length are not possible + * then the following holds. + * + * When offset is >= size() then after slice() size()==0. + * When length >= size()-offset then after slice() + * size() = old_size-offset. + * + @param offset The request new offset relative to the + * current offset. + @param length The requested new length. + * + @note offset and length are in units of sizeof(E). + * or bytes (1) when E=void. + */ + void slice(size_t offset, size_t length=(size_t)-1) + { + if(offset>m_total) + offset = m_total; + + m_offset += offset; + + m_total -= offset; + + if(offset>m_count) { + m_count = 0; + } else { + if(length > m_count - offset) + length = m_count - offset; + m_count = length; + } + } + + // Access to members. + const std::tr1::shared_ptr& dataPtr() const { return m_data; } + size_t dataOffset() const { return m_offset; } + size_t dataCount() const { return m_count; } + size_t dataTotal() const { return m_total; } + }; +} + +/** @brief A holder for a contigious piece of memory. + * + * Data is shared, but offset and length are not. + * This allows one vector to have access to only a + * subset of a piece of memory. + * + * The ways in which shared_vector is intended to differ from + * std::vector are outlined in @ref vectordiff . + * + * Also see @ref vectormem + */ +template +class shared_vector : public detail::shared_vector_base +{ + typedef detail::shared_vector_base base_t; +public: + typedef E value_type; + typedef E& reference; + typedef E* pointer; + typedef E* iterator; + typedef std::reverse_iterator reverse_iterator; + typedef ptrdiff_t difference_type; + typedef size_t size_type; + + typedef E element_type; + typedef std::tr1::shared_ptr shared_pointer_type; + + // allow specialization for all E to be friends + template friend class shared_vector; + + + //! @brief Empty vector (not very interesting) + shared_vector() :base_t() {} + + //! @brief Allocate (with new[]) a new vector of size c + explicit shared_vector(size_t c) + :base_t(new E[c], 0, c) + {} + + //! @brief Allocate (with new[]) a new vector of size c and fill with value e + shared_vector(size_t c, E e) + :base_t(new E[c], 0, c) + { + std::fill_n(this->m_data.get(), this->m_count, e); + } + + /** @brief Build vector from a raw pointer + * + @param v A raw pointer allocated with new[]. + @param o The offset in v or the first element visible to the vector + @param c The number of elements pointed to by v+o + */ + template + shared_vector(A v, size_t o, size_t c) :base_t(v,o,c) {} + + /** @brief Build vector from an existing smart pointer + * + @param d An existing smart pointer + @param o The offset in v or the first element visible to the vector + @param c The number of elements pointed to by v+o + */ + template + shared_vector(const std::tr1::shared_ptr& d, size_t o, size_t c) + :base_t(d,o,c) {} + + /** @brief Build vector from raw pointer and cleanup function + * + @param d An existing raw pointer + @param b An function/functor used to free d. Invoked as b(d). + @param o The offset in v or the first element visible to the vector + @param c The number of elements pointed to by v+o + */ + template + shared_vector(A d, B b, size_t o, size_t c) + :base_t(d,b,o,c) {} + + //! @brief Copy an existing vector of same or related type + template + shared_vector(const shared_vector& o) :base_t(o) {} + + size_t capacity() const { return this->m_total; } + + /** @brief Set array capacity + * + * A side effect is that array data will be uniquely owned by this instance + * as if make_unique() was called. This holds even if the capacity + * does not increase. + * + * For notes on copying see docs for make_exlcusive(). + */ + void reserve(size_t i) { + if(this->unique() && i<=this->m_total) + return; + pointer temp=new E[i]; + try{ + std::copy(begin(), end(), temp); + this->m_data.reset(temp, detail::default_array_deleter()); + }catch(...){ + delete[] temp; + throw; + } + this->m_offset = 0; + this->m_total = i; + // m_count is unchanged + } + + /** @brief Grow or shrink array + * + * A side effect is that array data will be uniquely owned by this instance + * as if make_unique() was called. This holds even if the size does not change. + * + * For notes on copying see docs for make_exlcusive(). + */ + void resize(size_t i) { + if(i==this->m_count) { + make_unique(); + return; + } + if(this->m_data && this->m_data.unique()) { + // we have data and exclusive ownership of it + if(i<=this->m_total) { + // We have room to grow! + this->m_count = i; + return; + } + } + // must re-allocate :( + size_t new_total = std::max(this->m_total, i); + pointer temp=new E[new_total]; + try{ + // Copy as much as possible from old, + // remaining elements are uninitialized. + std::copy(begin(), + begin()+std::min(i,this->size()), + temp); + this->m_data.reset(temp, detail::default_array_deleter()); + }catch(...){ + delete[] temp; + throw; + } + this->m_offset= 0; + this->m_count = i; + this->m_total = new_total; + } + + /** @brief Grow (and fill) or shrink array. + * + * see @ref resize(size_t) + */ + void resize(size_t i, E v) { + size_t oldsize=this->size(); + resize(i); + if(this->size()>oldsize) { + std::fill(begin()+oldsize, end(), v); + } + } + + /** @brief Ensure (by copying) that this shared_vector is the sole + * owner of the data array. + * + * If a copy is needed, memory is allocated with new[]. If this is + * not desireable then do something like the following. + @code + shared_vector original(...); + + if(!original.unique()){ + shared_vector temp(myallocator(original.size()), + 0, original.size()); + std::copy(original.begin(), original.end(), temp.begin()); + original.swap(temp); + } + assert(original.unique()); + @endcode + */ + void make_unique() { + if(this->unique()) + return; + shared_pointer_type d(new E[this->m_total], detail::default_array_deleter()); + std::copy(this->m_data.get()+this->m_offset, + this->m_data.get()+this->m_offset+this->m_count, + d.get()); + this->m_data.swap(d); + this->m_offset=0; + } + + + // STL iterators + + iterator begin() const{return this->m_data.get()+this->m_offset;} + + iterator end() const{return this->m_data.get()+this->m_offset+this->m_count;} + + reverse_iterator rbegin() const{return reverse_iterator(end());} + + reverse_iterator rend() const{return reverse_iterator(begin());} + + reference front() const{return (*this)[0];} + reference back() const{return (*this)[this->m_count-1];} + + // Modifications + + void push_back(const E& v) + { + resize(this->size()+1); + back() = v; + } + + void pop_back() + { + this->slice(0, this->size()-1); + } + + // data access + + pointer data() const{return this->m_data.get()+this->m_offset;} + + reference operator[](size_t i) const {return this->m_data.get()[this->m_offset+i];} + + reference at(size_t i) const + { + if(i>this->m_count) + throw std::out_of_range("Index out of bounds"); + return (*this)[i]; + } + +}; + +//! Specialization for storing untyped pointers +//! Does not allow access or iteration of contents +template<> +class shared_vector : public detail::shared_vector_base { + typedef detail::shared_vector_base base_t; +public: + typedef void* pointer; + typedef ptrdiff_t difference_type; + typedef size_t size_type; + + typedef std::tr1::shared_ptr shared_pointer_type; + + shared_vector() :base_t() {} + + template + shared_vector(A v, size_t o, size_t c) :base_t(v,o,c) {} + + template + shared_vector(const std::tr1::shared_ptr& d, size_t o, size_t c) + :base_t(d,o,c) {} + + template + shared_vector(A d, B b, size_t o, size_t c) + :base_t(d,b,o,c) {} + + template + shared_vector(const shared_vector& o) :base_t(o) {} + + pointer data() const{ + return (pointer)(((char*)this->m_data.get())+this->m_offset); + } +}; + +//! Specialization for storing constant untyped pointers +//! Does not allow access or iteration of contents +template<> +class shared_vector : public detail::shared_vector_base { + typedef detail::shared_vector_base base_t; +public: + typedef const void* pointer; + typedef ptrdiff_t difference_type; + typedef size_t size_type; + + typedef std::tr1::shared_ptr shared_pointer_type; + + shared_vector() :base_t() {} + + template + shared_vector(A v, size_t o, size_t c) :base_t(v,o,c) {} + + template + shared_vector(const std::tr1::shared_ptr& d, size_t o, size_t c) + :base_t(d,o,c) {} + + template + shared_vector(A d, B b, size_t o, size_t c) + :base_t(d,b,o,c) {} + + template + shared_vector(const shared_vector& o) :base_t(o) {} + + pointer data() const{ + return (pointer)(((char*)this->m_data.get())+this->m_offset); + } +}; + +template +class weak_vector { + std::tr1::weak_ptr m_data; + //! Offset in the data array of first element + size_t m_offset; + //! Number of elements between m_offset and end of data + size_t m_count; + +public: + typedef E element_type; + + weak_vector() + :m_data() + ,m_offset(0) + ,m_count(0) + {} + weak_vector(const weak_vector& o) + :m_data(o.m_data) + ,m_offset(o.m_offset) + ,m_count(o.m_count) + {} + weak_vector(const shared_vector& o) + :m_data(o.dataPtr()) + ,m_offset(o.dataOffset()) + ,m_count(o.dataCount()) + {} + + bool expired() const{return m_data.expired();} + shared_vector lock() const + { + return shared_vector(m_data.lock(), m_offset, m_count); + } + + void reset() + { + m_data.reset(); + m_offset = m_count = 0; + } + + void swap(weak_vector& o) + { + m_data.swap(o); + std::swap(m_offset, o.m_offset); + std::swap(m_count, o.m_count); + } +}; + +namespace detail { + template struct shared_vector_caster {}; + + template + struct shared_vector_caster { + static inline shared_vector op(const shared_vector& src) { + return shared_vector( + std::tr1::static_pointer_cast(src.dataPtr()), + src.dataOffset()*sizeof(FROM), + src.dataCount()*sizeof(FROM)); + } + }; + + template + struct shared_vector_caster { + static inline shared_vector op(const shared_vector& src) { + return shared_vector( + std::tr1::static_pointer_cast(src.dataPtr()), + src.dataOffset()/sizeof(TO), + src.dataCount()/sizeof(TO)); + } + }; +} + +/** @brief Allow casting of shared_vector between types + * + * Currently only to/from void is implemented. + * + @warning Casting from void is undefined unless the offset and length + * are integer multiples of the size of the destination type. + */ +template +static inline +shared_vector +static_shared_vector_cast(const shared_vector& src) +{ + return detail::shared_vector_caster::op(src); +} + +//! Allows casting from const TYPE -> TYPE. +template +static inline +shared_vector +const_shared_vector_cast(const shared_vector& src) +{ + return shared_vector( + std::tr1::const_pointer_cast(src.dataPtr()), + src.dataOffset(), + src.dataCount()); +} + + +}} // namespace epics::pvData + +// Global operators for shared_vector + +template +bool operator==(const epics::pvData::shared_vector& a, + const epics::pvData::shared_vector& b) +{ + if(a.size() != b.size()) + return false; + if(a.dataOffset()==b.dataOffset() && a.dataPtr().get()==b.dataPtr().get()) + return true; + return std::equal(a.begin(), a.end(), b.begin()); +} + +template +bool operator!=(const epics::pvData::shared_vector& a, + const epics::pvData::shared_vector& b) +{ + return !(a==b); +} + +template +std::ostream& operator<<(std::ostream& strm, const epics::pvData::shared_vector& arr) +{ + strm<<'{'<10) { + strm<<"..."; + break; + } + strm<' is 'const shared_vector'. However, + * it is also possible to have 'const shared_vector' analogous to + * 'E* const' and 'shared_vector' which is analogous to + * 'const E*'. + * + * Copying a shared_vector, by construction or assignment, does + * not copy its contents. Modifications to one such "copy" effect + * all associated shared_vector instances. + * + * std::vector::reserve(N) has no effect if N<=std::vector::capacity(). + * However, like resize(), shared_vector::reserve() has the side + * effect of always calling make_unique(). + * + * @section notimpl Parts of std::vector interface not implemented + * + * Mutating methods insert(), erase(), shrink_to_fit(), + * emplace(), and emplace_back() are not implemented. + * + * shared_vector does not model an allocator which is bound to the object. + * Therefore the get_allocator() method and the allocator_type typedef are + * not provided. + * + * shared_vector does not provide the const_* typedefs. + * + * The assign() method and the related constructor are not implemented + * at this time. + * + * The comparison operators '>', '>=', '<=', and '<' are not implemented + * at this time. + * + * @section newstuff Parts not found in std::vector + * + * shared_vector has additional constructors from raw pointers + * and shared_ptr s. + * + * The copy constructor and assignment operator allow implicit + * casting from type 'shared_vector' to 'shared_vector'. + * + * To faciliate safe modification the methods unique() and + * make_unique() are provided. + * + * The slice() method selects a sub-set of the shared_vector. + * + * The low level accessors dataPtr(), dataOffset(), dataCount(), + * and dataTotal(). + * + */ + +/** @page vectormem Memory Management with shared_vector + * + * The @link epics::pvData::shared_vector shared_vector class @endlink + * is a std::vector like class which implements sharing data by reference counting. + * + * Internally memory is tracked with the shared_ptr reference counting smart pointer. + * This allows a custom destructor to be specified. This allows a vector to borrow + * memory allocated by 3rd party libraries which require special cleanup. + * + * In place element modification is allowed. It is left to user code to ensure + * that such modification is safe, either from application specific knowledge, or by + * calling + * @link paramTable::string_data::make_unique make_unique @endlink + * explicitly, or implicitly by calling + * @link paramTable::shared_vector::resize resize @endlink + * prior to making modifications. + * + @code + extern "C" { + // array embedded in C structure + struct composite { + int other, stuff; + char buf[42]; + } + + // Unknown relation between array and handle + typedef void* handle_type; + handle_type mylib_alloc(void); + char *mylib_mem(handle_type); + void mylib_free(handle_type); + } + + // Note that mylibcleaner must be copy constructable + struct mylibcleaner { + handle_type handle; + mylibcleaner(handle_type h) :handle(h) {} + void operator()(char*){ mylib_free(handle);} + }; + + struct compcleaner { + void operator()(char* c){ free(c-offsetof(composite,buf)); } + }; + + void main() { + + unsigned char* buf=calloc(42,1); + + shared_vector a(buf, &free); + + a.clear(); // calls free(ptr) + + + composite *c=malloc(sizeof(*c)); + assert(c!=NULL); + + shared_vector d(c->buf, compcleaner()); + + d.clear(); // calls free(ptr-offsetof(composite,buf)) + + + void *handle=mylib_alloc(); + char *hmem=mylib_mem(handle); + assert(hmem!=NULL); + + shared_vector b(hmem, mylibcleaner(handle)); + + b.clear(); // calls mylib_free(handleptr) + } + @endcode + */