//# Block.h: Simple templated array classes //# Copyright (C) 1993-1997,2000,2002,2005,2015 //# Associated Universities, Inc. Washington DC, USA. //# National Astronomical Observatory of Japan //# 2-21-1, Osawa, Mitaka, Tokyo, 181-8588, Japan. //# //# This library is free software; you can redistribute it and/or modify it //# under the terms of the GNU Library General Public License as published by //# the Free Software Foundation; either version 2 of the License, or (at your //# option) any later version. //# //# This library is distributed in the hope that it will be useful, but WITHOUT //# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or //# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public //# License for more details. //# //# You should have received a copy of the GNU Library General Public License //# along with this library; if not, write to the Free Software Foundation, //# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA. //# //# Correspondence concerning AIPS++ should be addressed as follows: //# Internet email: aips2-request@nrao.edu. //# Postal address: AIPS++ Project Office //# National Radio Astronomy Observatory //# 520 Edgemont Road //# Charlottesville, VA 22903-2475 USA //# //# $Id$ #ifndef CASA_BLOCK_H #define CASA_BLOCK_H #include #include #include #include #include #include // for ptrdiff_t #include // for std:min/max #include //# For index checking #if defined(AIPS_ARRAY_INDEX_CHECK) #include #endif namespace casacore { //# NAMESPACE CASACORE - BEGIN // simple 1-D array // // // // // // // This should be viewed as a block of memory without sophisticated // manipulation functions. Thus it is called Block. // // // // Block is a simple templated 1-D array class. Indices are always // 0-based. For efficiency reasons, no index checking is done unless the // preprocessor symbol AIPS_ARRAY_INDEX_CHECK is defined. // Block's may be assigned to and constructed from other // Block's. // As no reference counting is done this can be an expensive operation, however. // // The net effect of this class is meant to be unsurprising to users who think // of arrays as first class objects. The name "Block" is intended to convey // the concept of a solid "chunk" of things without any intervening "fancy" // memory management, etc. This class was written to be // used in the implementations of more functional Vector, Matrix, etc. classes, // although it is expected Block will be useful on its own. // // The Block class should be efficient. You should normally use Block. // // If you use the assignment operator on an element of this // class, you may leave dangling references to pointers released from // storage(). // Resizing the array will also have this effect if the underlying storage // is actually affected. // // // If index checking is turned on, an out-of-bounds index will // generate an indexError exception. // // // // // Block a(100,0); // 100 ints initialized to 0 // Block b; // 0-length Block // // ... // b = a; // resize b and copy a into it // for (size_t i=0; i < a.nelements(); i++) { // a[i] = i; // Generate a sequence // // with Vectors, could simply say "indgen(myVector);" // } // b.set(-1); // All positions in b have the value -1 // b.resize(b.nelements()*2); // Make b twice as long, by default the old // // elements are copied over, although this can // // be defeated. // some_c_function(b.storage()); // Use a fn that takes an // // Int * pointer // // // class BlockTrace { public: // Set the trace size. The (de)allocation of Blocks with >= sz elements // will be traced using the MemoryTrace class. // A value 0 means no tracing. static void setTraceSize (size_t sz); protected: // Write alloc and free trace messages. static void doTraceAlloc (const void* addr, size_t nelem, DataType type, size_t sz); static void doTraceFree (const void* addr, size_t nelem, DataType type, size_t sz); protected: static size_t itsTraceSize; }; template class Block; template class Block_internal_IsFundamental { template friend class Block; static constexpr int value = static_cast(std::is_fundamental::value); }; template class Block_internal_IsPointer { template friend class Block; static constexpr int value = static_cast(std::is_pointer::value); }; // simple 1-D array // // // // // // // This should be viewed as a block of memory without sophisticated // manipulation functions. Thus it is called Block. // // // // Block is a simple templated 1-D array class. Indices are always // 0-based. For efficiency reasons, no index checking is done unless the // preprocessor symbol AIPS_ARRAY_INDEX_CHECK is defined. // Block's may be assigned to and constructed from other // Block's. // As no reference counting is done this can be an expensive operation, however. // // The net effect of this class is meant to be unsurprising to users who think // of arrays as first class objects. The name "Block" is intended to convey // the concept of a solid "chunk" of things without any intervening "fancy" // memory management, etc. This class was written to be // used in the implementations of more functional Vector, Matrix, etc. classes, // although it is expected Block will be useful on its own. // // The Block class should be efficient. You should normally use Block. // // If you use the assignment operator on an element of this // class, you may leave dangling references to pointers released from // storage(). // Resizing the array will also have this effect if the underlying storage // is actually affected. // // // If index checking is turned on, an out-of-bounds index will // generate an indexError exception. // // // // // Block a(100,0); // 100 ints initialized to 0 // Block b; // 0-length Block // // ... // b = a; // resize b and copy a into it // for (size_t i=0; i < a.nelements(); i++) { // a[i] = i; // Generate a sequence // // with Vectors, could simply say "indgen(myVector);" // } // b.set(-1); // All positions in b have the value -1 // b.resize(b.nelements()*2); // Make b twice as long, by default the old // // elements are copied over, although this can // // be defeated. // some_c_function(b.storage()); // Use a fn that takes an // // Int * pointer // // // template class Block: public BlockTrace { public: // Create a zero-length Block. Note that any index into this Block // is an error. // DefaultAllocator is used as an allocator. Block() : allocator_p(get_allocator::type>()), capacity_p( 0), used_p(0), array(0), destroyPointer(True), keep_allocator_p(False) { } // Create a zero-length Block. Note that any index into this Block // is an error. template explicit Block(AllocSpec const &) : allocator_p(get_allocator()), capacity_p(0), used_p( 0), array(0), destroyPointer(True), keep_allocator_p(False) { } // Create a Block with the given number of points. The values in Block // are initialized. Note that indices range between 0 and n-1. // DefaultAllocator is used as an allocator. explicit Block(size_t n) : allocator_p(get_allocator::type>()), used_p( n), destroyPointer(True), keep_allocator_p(False) { init(init_anyway() ? ArrayInitPolicies::INIT : ArrayInitPolicies::NO_INIT); } // Create a Block with the given number of points. The values in Block // are initialized. Note that indices range between 0 and n-1. template Block(size_t n, AllocSpec const &) : allocator_p(get_allocator()), used_p(n), destroyPointer( True), keep_allocator_p(False) { init(init_anyway() ? ArrayInitPolicies::INIT : ArrayInitPolicies::NO_INIT); } // Create a Block with the given number of points. The values in Block // are uninitialized. Note that indices range between 0 and n-1. // DefaultAllocator is used as an allocator. Block(size_t n, ArrayInitPolicy initPolicy) : allocator_p(get_allocator::type>()), used_p( n), destroyPointer(True), keep_allocator_p(False) { init(initPolicy); } // Create a Block with the given number of points. // Note that indices range between 0 and n-1. template Block(size_t n, ArrayInitPolicy initPolicy, AllocSpec const &) : allocator_p(get_allocator()), used_p(n), destroyPointer( True), keep_allocator_p(False) { init(initPolicy); } // Create a Block of the given length, and initialize (via copy constructor for // objects of type T) with the provided value. // DefaultAllocator is used as an allocator. Block(size_t n, T const &val) : allocator_p(get_allocator::type>()), used_p( n), destroyPointer(True), keep_allocator_p(False) { init(ArrayInitPolicies::NO_INIT); try { allocator_p->construct(array, get_size(), val); } catch (...) { dealloc(); throw; } } // Create a Block of the given length, and initialize (via copy constructor for // objects of type T) with the provided value. template Block(size_t n, T const &val, AllocSpec const &) : allocator_p(get_allocator()), used_p(n), destroyPointer( True), keep_allocator_p(False) { init(ArrayInitPolicies::NO_INIT); try { allocator_p->construct(array, get_size(), val); } catch (...) { dealloc(); throw; } } // Create a Block from a C-array (i.e. pointer). If // takeOverStorage is True, The Block assumes that // it owns the pointer, i.e. that it is safe to release it via allocator when // the Block is destructed, otherwise the actual storage is not destroyed. // If true, storagePointer is set to 0. // It is strongly recommended to supply an appropriate allocator argument explicitly // whenever takeOverStorage == True // to let Block to know how to release the storagePointer. // The default allocator set by this constructor will be changed from NewDelAllocator::value // to DefaultAllocator::value in future. Block(size_t n, T *&storagePointer, Bool takeOverStorage = True) : allocator_p(get_allocator::type>()), capacity_p( n), used_p(n), array(storagePointer), destroyPointer(takeOverStorage), keep_allocator_p( False) { if (destroyPointer) storagePointer = 0; } // Create a Block from a C-array (i.e. pointer). If // takeOverStorage is True, The Block assumes that // it owns the pointer, i.e. that it is safe to release it via allocator when // the Block is destructed, otherwise the actual storage is not destroyed. // If true, storagePointer is set to 0. template Block(size_t n, T *&storagePointer, Bool takeOverStorage, AllocSpec const &) : allocator_p(get_allocator()), capacity_p(n), used_p( n), array(storagePointer), destroyPointer(takeOverStorage), keep_allocator_p( False) { if (destroyPointer) storagePointer = 0; } // Copy the other block into this one. Uses copy, not reference, semantics. Block(const Block &other) : allocator_p(other.allocator_p), used_p(other.size()), destroyPointer( True), keep_allocator_p(False) { init(ArrayInitPolicies::NO_INIT); try { //objcopy(array, other.array, get_size()); objthrowcp1(array, other.array, get_size()); allocator_p->construct(array, get_size(), other.array); } catch (...) { dealloc(); throw; } } // Assign other to this. this resizes itself to the size of other, so after // the assignment, this->nelements() == other.nelements() always. Block &operator=(const Block &other) { if (&other != this) { T *old = array; this->resize(other.size(), True, False, ArrayInitPolicies::NO_INIT); if (array == old) { objcopy(array, other.array, get_size()); } else { objthrowcp1(array, other.array, get_size()); allocator_p->construct(array, get_size(), other.array); } } return *this; } // Frees up the storage pointed contained in the Block. ~Block() { deinit(); } // Resizes the Block. If n == nelements() resize just returns. If // a larger size is requested (n > nelements()) the Block always // resizes. If the requested size is smaller (n < nelements()), // by default the Block does not resize smaller, although it can be // forced to with forceSmaller. The reasoning behind this is that // often the user will just want a buffer of at least a certain size, // and won't want to pay the cost of multiple resizings. // // Block bf(100, 0.0); // bf.resize(10); // bf.nelements() == 100 // bf.resize(10, True) // bf.nelements() == 10 // bf.resize(200) // bf.nelements() == 200 // // Normally the old elements are copied over (although if the // Block is lengthened the trailing elements will have undefined // values), however this can be turned off by setting copyElements to // False. // // This is written as three functions because default parameters do // not always work properly with templates. // // initPolicy makes sense to determine whether extended elements // should be initialized or not when you enlarge Block. // void resize(size_t n, Bool forceSmaller = False, Bool copyElements = True) { resize(n, forceSmaller, copyElements, init_anyway() ? ArrayInitPolicies::INIT : ArrayInitPolicies::NO_INIT); } void resize(size_t n, Bool forceSmaller, Bool copyElements, ArrayInitPolicy initPolicy) { if (n == get_size()) { return; } if (n < get_size() && forceSmaller == False) { if (false) { // to keep get_size() == get_capacity() allocator_p->destroy(&array[n], get_size() - n); set_size(n); } return; } if (get_size() < n && n <= get_capacity()) { allocator_p->construct(&array[get_size()], n - get_size()); set_size(n); return; } T *tp = n > 0 ? allocator_p->allocate(n) : 0; traceAlloc(tp, n); if (n > 0) { size_t start = 0; if (copyElements) { size_t nmin = std::min(get_size(), n); // Don't copy too much! if (nmin > 0) { try { allocator_p->construct(tp, nmin, array); } catch (...) { traceFree(tp, n); allocator_p->deallocate(tp, n); throw; } } start = nmin; } if (initPolicy == ArrayInitPolicies::INIT) { try { allocator_p->construct(&tp[start], n - start); } catch (...) { allocator_p->destroy(tp, start); traceFree(tp, n); allocator_p->deallocate(tp, n); throw; } } } deinit(); destroyPointer = True; array = tp; // ... and update pointer set_capacity(n); set_size(n); } // // Remove a single element from the Block. If forceSmaller is True this // will resize the Block and hence involve new memory allocations. This is // relatively expensive so setting forceSmaller to False is preferred. When // forceSmaller is False the Block is not resized but the elements with an // index above the removed element are shuffled down by one. For backward // compatibility forceSmaller is True by default. // // initPolicy makes sense to determine whether new storage // should be initialized or not before copying when forceSmaller is True. // void remove(size_t whichOne, Bool forceSmaller = True) { remove(whichOne, forceSmaller, init_anyway() ? ArrayInitPolicies::INIT : ArrayInitPolicies::NO_INIT); } void remove(size_t whichOne, Bool forceSmaller, ArrayInitPolicy initPolicy) { if (whichOne >= get_size()) { #if defined(AIPS_ARRAY_INDEX_CHECK) throw(indexError(whichOne, "Block::remove() - " "index out of range")); #else return; #endif } size_t n = get_size() - 1; if (forceSmaller == True) { T *tp = n > 0 ? allocator_p->allocate(n) : 0; traceAlloc(array, n); if (initPolicy == ArrayInitPolicies::INIT && n > 0) { try { allocator_p->construct(tp, n); } catch (...) { traceFree(tp, n); allocator_p->deallocate(tp, n); throw; } } try { objcopy(tp, array, whichOne); } catch (...) { traceFree(tp, n); allocator_p->deallocate(tp, n); throw; } try { objcopy(tp + whichOne, array + whichOne + 1, get_size() - whichOne - 1); } catch (...) { allocator_p->destroy(tp, whichOne); traceFree(tp, n); allocator_p->deallocate(tp, n); throw; } if (array && destroyPointer) { traceFree(array, get_capacity()); allocator_p->destroy(array, get_size()); allocator_p->deallocate(array, get_capacity()); array = 0; }; set_capacity(n); set_size(n); array = tp; destroyPointer = True; } else { objmove(&array[whichOne], &array[whichOne + 1], get_size() - whichOne - 1); if (false) { // to keep get_size() == get_capacity() allocator_p->destroy(&array[n], 1); set_size(n); } } } // // Prohibit changing allocator for this instance. // void prohibitChangingAllocator() { keep_allocator_p = True; } // Permit changing allocator for this instance. void permitChangingAllocator() { keep_allocator_p = False; } // // Replace the internal storage with a C-array (i.e. pointer). // If takeOverStorage is True, The Block assumes that it // owns the pointer, i.e. that it is safe to release it via allocator when the // Blockis destructed, otherwise the actual storage is not destroyed. // If true, storagePointer is set to NULL. // It is strongly recommended to supply an appropriate allocator argument explicitly // whenever takeOverStorage == True // to let Block to know how to release the storagePointer. // The default parameter of allocator will be changed from AllocSpec >::value // to AllocSpec >::value in future. // AipsError is thrown if allocator is incompatible with the current allocator of the instance and changing allocator is prohibited, // even if takeOverStorage == False. // void replaceStorage(size_t n, T *&storagePointer, Bool takeOverStorage=True) { replaceStorage(n, storagePointer, takeOverStorage, AllocSpec >::value); } template void replaceStorage(size_t n, T *&storagePointer, Bool takeOverStorage, AllocSpec const &) { if (keep_allocator_p && ! isCompatibleAllocator()) { throw AipsError("Block::replaceStorage - Attemption to change allocator of Block"); } if (array && destroyPointer) { traceFree (array, get_capacity()); allocator_p->destroy(array, get_size()); allocator_p->deallocate(array, get_capacity()); array = 0; }; set_capacity(n); set_size(n); allocator_p = get_allocator(); array = storagePointer; destroyPointer = takeOverStorage; if (destroyPointer) storagePointer = 0; } // // Index into the block (0-based). If the preprocessor symbol // AIPS_ARRAY_INDEX_CHECK is defined, index checking will be done // and an out-of-bounds index will cause an indexError to be // thrown. Note that valid indices range between 0 and nelements()-1. // //
  • indexError // // T &operator[](size_t index) { #if defined(AIPS_ARRAY_INDEX_CHECK) // Write it this way to avoid casts; remember index and get_size() are // unsigned. if ((get_size() == 0) || (index > get_size() - 1)) { throw(indexError(index, "Block::operator[] - " "index out of range")); }; #endif return array[index]; } const T &operator[](size_t index) const { #if defined(AIPS_ARRAY_INDEX_CHECK) if ((get_size() == 0) || (index > get_size() - 1)) { throw(indexError(index, "Block::operator[] const - " "index out of range")); }; #endif return array[index]; } // // Set all values in the block to "val". // Block &operator=(const T &val) { T tmp=val; objset(array, tmp, get_size()); return *this;} void set(const T &val) { *this = val; } // // If you really, really, need a "raw" pointer to the beginning of the // storage area this will give it to you. This may leave dangling pointers // if the block is destructed or if the assignment operator or resize // is used. Returns a null pointer if nelements() == 0. // It is best to only use this if you completely control the extent and // lifetime of the Block. //

    Examples of misuse

    // Block *bp = new Block(100); // Int *ip = bp->storage(); // DefaultAllocator::value.deallocate(bp, bp->capacity()); // Oops, ip is now dangling // Block a(100),b(100); // Int *ip = a.storage(); // a = b; // Likewise // // T *storage() {return array;} const T *storage() const {return array;} // // The number of elements contained in this Block. // size_t nelements() const {return size();} size_t size() const {return get_capacity();} // // The capacity in this Block. // size() <= capacity() is always true. size_t capacity() const {return get_capacity();} // Is the block empty (i.e. no elements)? Bool empty() const {return size() == 0;} // Define the STL-style iterators. // It makes it possible to iterate through all data elements. // // Block bl(100,0); // for (Block::iterator iter=bl.begin(); iter!=bl.end(); iter++) { // *iter += 1; // } // // // STL-style typedefs. // typedef T value_type; typedef T* iterator; typedef const T* const_iterator; typedef value_type* pointer; typedef const value_type* const_pointer; typedef value_type& reference; typedef const value_type& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; // // Get the begin and end iterator object for this block. // iterator begin() { return array; } const_iterator begin() const { return array; } iterator end() { return array + size(); } const_iterator end() const { return array + size(); } // // inline void traceAlloc (const void* addr, size_t sz) const { if (itsTraceSize>0 && sz>=itsTraceSize) { doTraceAlloc (addr, sz, whatType(static_cast(0)), sizeof(T)); } } inline void traceFree (const void* addr, size_t sz) const { if (itsTraceSize>0 && sz>=itsTraceSize) { doTraceFree (addr, sz, whatType(static_cast(0)), sizeof(T)); } } private: friend class Array; // to allow access to following constructors. Block(size_t n, ArrayInitPolicy initPolicy, Allocator_private::BulkAllocator *allocator) : allocator_p(allocator), used_p(n), destroyPointer( True), keep_allocator_p(False) { init(initPolicy); } Block(size_t n, Allocator_private::AllocSpec allocator) : allocator_p(allocator.allocator), used_p(n), destroyPointer( True), keep_allocator_p(False) { init(init_anyway() ? ArrayInitPolicies::INIT : ArrayInitPolicies::NO_INIT); } Block(size_t n, T *&storagePointer, Bool takeOverStorage, Allocator_private::BulkAllocator *allocator) : allocator_p(allocator), capacity_p(n), used_p( n), array(storagePointer), destroyPointer(takeOverStorage), keep_allocator_p( False) { if (destroyPointer) storagePointer = 0; } void construct(size_t pos, size_t n, T const *src) { allocator_p->construct(&array[pos], n, src); } void construct(size_t pos, size_t n, T const &initial_value) { allocator_p->construct(&array[pos], n, initial_value); } void construct(size_t pos, size_type n) { allocator_p->construct(&array[pos], n); } void destroy(size_t pos, size_type n) { allocator_p->destroy(&array[pos], n); } Allocator_private::BulkAllocator *get_allocator(){ return allocator_p; } static bool init_anyway() { return !(Block_internal_IsFundamental::value || Block_internal_IsPointer::value); } // end of friend void init(ArrayInitPolicy initPolicy) { set_capacity(get_size()); if (get_capacity() > 0) { array = allocator_p->allocate(get_capacity()); traceAlloc(array, get_capacity()); if (initPolicy == ArrayInitPolicies::INIT) { try { allocator_p->construct(array, get_size()); } catch (...) { dealloc(); throw; } } } else { array = 0; } } void deinit() { if (array && destroyPointer) { allocator_p->destroy(array, get_size()); dealloc(); } } void dealloc() { if (array && destroyPointer) { traceFree(array, get_capacity()); allocator_p->deallocate(array, get_capacity()); array = 0; } } template static typename Allocator_private::BulkAllocator< typename Allocator::value_type> *get_allocator() { return Allocator_private::get_allocator< Allocator>(); } template Bool isCompatibleAllocator() { typename Allocator_private::BulkAllocator< typename Allocator::type::value_type> *other_allocator = Allocator_private::get_allocator(); return other_allocator == allocator_p; } // The number of used elements in the vector size_t get_size() const { return used_p;} // Set the number of used elements in the vector void set_size(size_t new_value) { AlwaysAssert(new_value <= get_capacity(), AipsError); used_p = new_value; } // The capacity of the vector size_t get_capacity() const { return capacity_p;} // Set the capacity of the vector void set_capacity(size_t new_value) { capacity_p = new_value; set_size(std::min(get_size(), capacity_p)); } // The allocator typename Allocator_private::BulkAllocator *allocator_p; // The capacity of the vector size_t capacity_p; // The number of used elements in the vector size_t used_p; // The actual storage T *array; // Can we delete the storage upon destruction? Bool destroyPointer; // Can we change allocator or not? Bool keep_allocator_p; }; // // A drop-in replacement for Block. // // // //
  • Block // // // PtrBlock has exactly the same interface as Block // and should be used in preference to the latter. It's purpose is solely to // reduce the number of template instantiations. // // //
  • Partial template specialization is another implementation choice that // will be possible eventually. //
  • It might be useful to have functions that know the template parameter // is a pointer, e.g. that delete all the non-null pointers. // template class PtrBlock { public: PtrBlock() : block_p() {} explicit PtrBlock(size_t n) : block_p(n) {} PtrBlock(size_t n, T val) : block_p(n, (void *)val) {} PtrBlock(size_t n, T *&storagePointer, Bool takeOverStorage = True) : block_p(n, (void **&)storagePointer, takeOverStorage) {} PtrBlock(const PtrBlock &other) : block_p(other.block_p) {} PtrBlock &operator=(const PtrBlock &other) { block_p = other.block_p; return *this;} ~PtrBlock() {} void resize(size_t n, Bool forceSmaller, Bool copyElements) { block_p.resize(n,forceSmaller, copyElements); } void resize(size_t n) {block_p.resize(n);} void resize(size_t n, Bool forceSmaller) {block_p.resize(n, forceSmaller);} void remove(size_t whichOne, Bool forceSmaller) { block_p.remove(whichOne, forceSmaller);} void remove(size_t whichOne) {block_p.remove(whichOne);} void replaceStorage(size_t n, T *&storagePointer, Bool takeOverStorage=True) {block_p.replaceStorage(n, (void **&)storagePointer, takeOverStorage);} T &operator[](size_t index) {return (T &)block_p[index];} const T &operator[](size_t index) const {return (const T &)block_p[index];} void set(const T &val) {block_p.set((void *const &)val);} PtrBlock &operator=(const T &val) {set(val); return *this;} T *storage() {return (T *)block_p.storage();} const T *storage() const {return (const T *)block_p.storage();} size_t nelements() const {return block_p.nelements();} size_t size() const {return block_p.size();} Bool empty() const {return block_p.empty();} private: Block block_p; }; //# Instantiate extern templates for often used types. extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; extern template class Block; } //# NAMESPACE CASACORE - END #endif