The containers included here are intended to extend the stl containers. In all cases, these implementations are designed to implement c++ conveniences and safety where speed is not sacrificed. Also includes techniques for memory debugging of common problems such as buffer overruns.
The containers included here are intended to extend the stl containers in a way that provides features not included in the stl or to provide alternatives for stl containers that have been designed for convenience, not speed. Here the emphasis is on speed first, and c++ conveniences second.
Include the following at the top of any translation unit which requires this library:
#include <ecl/containers.hpp> // The container types using ecl::Array; using ecl::Stencil; using ecl::FiFo using ecl::PushAndPop;
These classes are template classes, so no linking is required for these interfaces.
Arrays provide a fixed size container (enabled via a template parameter) that can be saved as contiguous memory on the stack. This is in contrast to typical stl containers that are dynamic and thus use the heap for storage, which is much slower (on my dual-core, often about 10% slower for access methods and up to 500% slower for construction).
It provides all the usual stl iterators associated with the object, as well as many of the access methods excepting the size-changing ones. There are, however a couple of features of arrays that stand out from stl containers.
Comma Initialisation
This concept is brought in (with slightly modified form) from Blitz++ and Eigen. It enables initialisation via the comma operator.
ecl::Array<int,5> array; // be careful, at this point, the contents are uninitialised. array << 1,2,3,4,5; // comma initialisation
Comman initialisers will throw an exception if the range is exceeded in debug mode (beware release mode!).
BluePrint Instantiations
These provide an efficient means of instantiating array classes without a costly copy-on-the-fly slowdown. It uses a blueprint coupled with what is referred to as a blueprint factory to do this (refer to ecl_concepts for more details.). However, the construction makes the usage of this transparent.
Array<int,5> array = Array<int,5>::Constant(3); // construction. array = Array<int,5>::Constant(3); // assignment, no costly copy operation used.
These arrays are almost exactly the same as the former fixed arrays, however they are dynamically sizeable (though not as automatically nor flexibly as vectors) with their storage on the heap, not the stack. The lack of flexibility isn't a serious disadvantage as a control program should generally manage vector memory careful (read, manually!) anyway. Their other feature is again, like the fixed arrays, the comma initialiser.
Array<int> array1; // Note the similarity to fixed arrays - no storage allocated with this constructor! Array<int> array2(4); // Initialise a vector with storage space for four integers. Array<int> array3 = Array<int>::Constant(4,3); // Initialise space for four integers filled with a value of 3. array1.clear(); // Clear - blows away the previous contents and leaves the buffer pointing to NULL. array1.resize(10); // Resize - blows away previous contents and leaves values uninitialised.
Arrays also provide a feature to allow for buffer overflow checking - for both fixed and dynamic arrays. Often programs pull pointers to various elements on the contiguous array (especially in really low level routines like vision routines), it is important to know when the stack or heap is being under or over run. Such functionality can be enabled by declaring the ECL_MEM_CHECK_ARRAYS macro when compiling (maybe later I'll bring this in as a template parameter).
Internally, this entails pre/appending some magic characters to the underlying array and then occasionally performing manual checks to ensure that the magic chars haven't been disturbed.
#define ECL_MEM_CHECK_ARRAYS Array<int,4> array; array << 1, 2, 3, 4; while ( control_loop_running ) { int* istart = array[0]; // send istart off to do some work elsewhere, possibly overrunning or underrunning. // ... if ( array.bufferUnderRun() ) { std::cout << "WARNING! We have an underrun problem houston!" << std::endl; } if ( array.bufferOverRun() ) { std::cout << "WARNING! We have an overrun problem houston!" << std::endl; } if ( array.bufferOverFlow() ) { std::cout << "WARNING! We have an unknown problem houston!" << std::endl; } }
Often you require a window onto a container (array) and stencils provide a safe way to do this. It avoids the use of dangerous pointers which can cause problems if the underlying container goes out of scope or is resized. It does this by including the underlying container as a reference and also includes similar out of range checks that arrays perform (exception throwing in debug mode for [] and always for at()).
typedef Array<int,5> FixedArray; typedef Stencil< FixedArray > FixedStencil; FixedArray array; array << 1,2,3,4,5; FixedStencil stencil(array,1,3); // windows from index 1 to 3 std::cout << stencil[1] << std::endl; // accesses array[2];
During the construction of fifo, typically length of buffer may be important.
FiFo<double> fifo(4); // reserved only (not filled)
fifo.push_back( data_incoming );
An initialised buffer can be important in variaous situation. For example, if you wish to calculate a moving average, you should wait for N (length of buffer) pieces of incoming of data. However, if you initialise the whole buffer as above, you can proceed immediately.
FiFo<double> fifo_initialised(4, initial_value); // reserved and filled fifo.push_back( next_data ); // set next data // ... // calculate the average here
Some converters utilising ecl::converters::FromByteArray are enabled for arrays and stencils respectively. These allow conversion to integers of any time from a corresponding, little endian filled array/stencil of chars.
typedef Array<char, 4> ByteArray; ByteArray byte_array; byte_array << 0x01, 0x02, 0x03; ecl::Converter<int,ByteStencil> toInt; std::cout << "Conversion: " << toInt(byte_array) << std::endl; // results in 197121 std::cout << "Conversion: " << toInt(byte_array.stencil(0,2)) << std::endl; // only first two bytes -> 513
and going the other way, we pass it the byte array for storage when converting (works on both arrays and stencils)...
Array<char,20> byte_array = Array<char,20>::Constant(0x00); Stencil< Array<char, 20> > stencil = byte_array.stencil(2,4); ecl::Converter< Stencil< Array<char, 20> >, ecl::int32 > fromInt; fromInt(stencil, ecl::int32(197121)); std::cout << byte_array << std::endl; // 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, ... std::cout << stencil << std::endl; // 0x01, 0x02, 0x03, 0x00
Currently there is only formatters for the array and stencil classes classes which do:
#include <ecl/array.hpp> Array<unsigned char>::Formatter format; Array<unsigned char> array; array << 0x01, 0x02, 0x03, 0x04; std::cout << format(array) << std::endl; // OR #include <ecl/array.hpp> #include <ecl/format.hpp> Format< Array<unsigned char> > format; Array<unsigned char> array; array << 0x01, 0x02, 0x03, 0x04; std::cout << format(array) << std::endl;