|
vector
Code below shows a template based vector class, similar to std::vector by design when method names are the same, but otherwise varying any way it suits me. When you port code using C++ std::vector to an app where no standard C++ libraries can be linked, you might be able to use cy::Vt below instead, if equivalent methods are supported. Public methods in cy::Vt are modeled on api in std::vector. But method names all have a v prefix. Nested classes start with V and otherwise have terse names. Abbreviations include: t for template, e for element, z for slice, p for pointer (ie iterator), r for reverse, k for const, q for quote, o for out stream, n for number (ie size), x for max (or example), i for integer, v for vector. (See also thorn names.)
purpose
The purpose of cy::Vt is to replace std::vector (in code using a small part of the entire STL api in standard vectors) when standard C++ libraries cannot be used, and when C clients need full control over memory allocation. In particular, the purpose of cy::Vt is to ensure vector elements are properly constructed and destroyed, so any resources controlled are correctly managed. When you use in C++ in private implementations under a C api, it's sometimes awkward to use RAII (resource acquisition is initialization) style coding without a template based vector class focusing on object invariants of sequence elements. Specifically, cy::Vt is used in a row clone of deck which used std::vector in first drafts. Actual vector api used in deck provided a spec for what cy::Vt needed. (Anything else was overkill added for thinking purposes.)
invariants
Vector elements are always constructed right after allocation, and destroyed right before deallocation. Unused vector elements are not maintained in a non-constructed state of raw space. This seems to use slightly more resources than necessary (assuming constructed objects are more costly) Maximum efficiency under all operations is not one of the invariants. Some operations (like erasing a single vector element) move all elements afterward, one at a time. Obviously this is rarely a good idea in code used frequently. So cy::Vt is intended mainly for use when editing is uncommon.
templates
Without templates, it's hard to maintain an invariant that allocated C++ objects are properly constructed when element types can change each time a collection instantiates. If you never wrote a template class, it might seem strange that all of each template is written as inline code. This interferes a lot with any habits and conventions you use for dividing code into inline methods and normal functions compiled separately. How should you deal with this? Don't worry about it. Assume compilers optimize results well enough. However, sometimes you can move high cost code out of a template into a class used by the template. Almost nothing like this appears in cy::Vt, unless you count constructors and destructors (and assignment operators) for element type E. Also, space allocation is farmed out to cy_pool. You may get a nagging feeling: complex inline methods calling other complex inline methods is wrong. But templates are always irritating that way. Relax! Worry about other things.
cy
Code without prefix cy_ appears in the cy namespace. namespace cy {
///// ----- Vt ----- /////
template <typename E> class Vt; // forward delaration
///// ----- Vcut ----- /////
#define Vt_DUMP_CITE 1
Vt « Here's the start of Vt. It's easy to miss because the nested classes inside appear flush left, the way you'd expect for code with global scope. (For example, Vcut is named Vt::Vcut, but inside Vt code Vcut is enough.) Template parameter E is the element type. It needs an empty constructor and a destructor because Vt calls them explicitly in _vnew() and _vdelete(). If you want an element to be a primitive type like an integer, just use a struct wrapper with constructor and destructor. ///// ----- Vt ----- /////
template <typename E> // vector of E elem (like std::vector)
class Vt { // vector of type t template
public:
Vcut « Cut is a synonym for slice. See misc for code defining cy::Cut which clones yz from the slice thorn demo. Although Vt::Vcut is not used on this page, it would be used by hypothetical code slicing Vt vectors as subsets. ///// ----- Vt::Vcut ----- /////
struct Vcut : public Cut { // slice of Vt
Vt& z_v;
Vcut(zp32 p, zn32 n, Vt const& v)
: Cut(p, n), z_v(*(Vt*)&v) { }
Vcut(Cut const& z, Vt const& v)
: Cut(z), z_v(*(Vt*)&v) { }
struct Zq {
Vcut const& q_z; Zq(Vcut const& z): q_z(z) { }
};
Zq quote() const { return Zq(*this); } // to request dump
}; // Vcut
Ve « Ve is a vector element, or rather, points to one. The state is very simple: it's just a E pointer named p_e. The original thorn name was yvtp in the global scope. (This explains why methods begin with p for pointer/iterator.) All Vt iterators subclass Ve so they can inherit methods using that pointer. All the horrific api is just convenient access to the E pointer member p_e. This takes care of construction, assignment, and comparison of E pointers in iterators. ///// ----- Vt::Ve ----- /////
class Ve { // vector element
public: E* p_e; public:
Ve() : p_e(0) { }
explicit Ve(const E* e) : p_e((E*) e) { }
Ve(Ve const& t) : p_e(t.p_e) { }
Ve& operator=(const E* e) { p_e = (E*) e; return *this; }
Ve& operator=(Ve const& t) { p_e = t.p_e; return *this; }
int pcmp(Ve const& t) const { return (p_e - t.p_e); }
int operator-(Ve t) const { return (p_e - t.p_e); }
bool operator==(Ve const& t) const { return (p_e == t.p_e); }
bool operator!=(Ve const& t) const { return (p_e != t.p_e); }
bool operator> (Ve const& t) const { return (p_e > t.p_e); }
bool operator>=(Ve const& t) const { return (p_e >= t.p_e); }
bool operator< (Ve const& t) const { return (p_e < t.p_e); }
bool operator<=(Ve const& t) const { return (p_e <= t.p_e); }
E* pe() const { return this->p_e; }
const E* kpe() const { return this->p_e; }
operator const E*() const { return this->p_e; }
Ve operator+(int i) { return Ve(this->p_e + i); }
Ve operator-(int i) { return Ve(this->p_e - i); }
}; // Ve
Vp « Vp is a forward iterator. (Thorn iterators are called pointers because they act just like pointers, thus the use of p.) Postfix operators are never honored in thorn and cy classes. Postfix does the same thing as prefix, instead of delaying the side effect until later. The main purpose of Vp is to define the value returned by vbegin() and vend(). Implicitly, both Vt and iterators like Vp assume a basic part of Vt's contiguity model: all vector elements are contiguous. (In thorn, vectors are all contiguous, while an array with the same api as a vector can be discontiguous. Thus, in scatter/gather code, the term array denotes an api like a vector which deals in discontiguous members.) Because vector elements are contiguous, iterators can simply use pointers themselves to implement iteration. Yes, this is just as trivial as it seems. ///// ----- Vt::Vp ----- /////
class Vp : public Ve { // vector pointer (iterator)
public:
Vp() { }
explicit Vp(const E* e) : Ve((E*) e) { }
Vp(Vp const& t) : Ve(t) { }
Vp& operator=(const E* e) {
this->p_e = (E*) e; return *this;
}
Vp& operator=(Ve const& t) {
this->p_e = t.p_e; return *this;
}
operator E*() const { return this->p_e; }
E& operator*() const { return *this->p_e; }
Vp& operator++() { ++this->p_e; return *this; } // prefix
Vp& operator--() { --this->p_e; return *this; } // prefix
Vp operator++(int) { Vp p(*this); this->p_e++; return p; }
Vp operator--(int) { Vp p(*this); this->p_e--; return p; }
//Vp operator+(int i) { return Vp(this->p_e + i); }
//Vp operator-(int i) { return Vp(this->p_e - i); }
}; // Vp template iter (pointer)
Vrp « Vrp is a reverse iterator. It's exactly like Vp but increment is down and decrement up. ///// ----- Vt::Vrp ----- /////
class Vrp : public Ve { public:
Vrp() { }
explicit Vrp(const E* e) : Ve((E*) e) { }
Vrp(Vrp const& t) : Ve(t) { }
Vrp& operator=(Ve const& t) {
this->p_e = t.p_e; return *this;
}
Vrp& operator=(const E* e) {
this->p_e = (E*) e; return *this;
}
operator E*() const { return this->p_e; }
E& operator*() const { return *this->p_e; }
Vrp& operator++() { --this->p_e; return *this; } // prefix
Vrp& operator--() { ++this->p_e; return *this; } // prefix
Vrp operator++(int) { Vrp p(*this); this->p_e--; return p; }
Vrp operator--(int) { Vrp p(*this); this->p_e++; return p; }
}; // Vrp reverse template iter (pointer)
Vkp « Vkp below is a const iterator. It's exactly like Vp, except the E inside is consistently assumed immutable. The purpose of this is to induce compiler warnings when you inadvertantly try to alter an element in a const vector while iterating. ///// ----- Vt::Vkp ----- /////
class Vkp : public Ve { public:
Vkp() { }
Vkp(Vkp const& t) : Ve(t) { }
Vkp(Vp const& t) : Ve(t) { }
explicit Vkp(const E* e) : Ve((E*)e) { }
Vkp& operator=(Ve const& t) {
this->p_e = t.p_e; return *this;
}
Vkp& operator=(const E* e) {
this->p_e = (E*) e; return *this;
}
operator const E*() const { return this->p_e; }
const E& operator*() const { return *this->p_e; }
Vkp& operator++() { ++this->p_e; return *this; } // prefix
Vkp& operator--() { --this->p_e; return *this; } // prefix
Vkp operator++(int) { Vkp p(*this); this->p_e++; return p; }
Vkp operator--(int) { Vkp p(*this); this->p_e--; return p; }
}; // Vkp const template iter (pointer)
Vkrp « Vkrp below is a reverse const iterator. It's exactly like Vkp, but increment is down and decrement up. ///// ----- Vt::Vkrp ----- /////
class Vkrp : public Ve { public:
Vkrp() { }
explicit Vkrp(const E* e) : Ve((E*)e) { }
explicit Vkrp(Vkrp const& t) : Ve(t) { }
explicit Vkrp(Vrp const& t) : Ve(t) { }
Vkrp& operator=(Ve const& t) {
this->p_e = t.p_e; return *this;
}
Vkrp& operator=(const E* e) {
this->p_e = (E*) e; return *this;
}
operator const E*() const { return this->p_e; }
const E& operator*() const { return *this->p_e; }
Vkrp& operator++() { --this->p_e; return *this; } // prefix
Vkrp& operator--() { ++this->p_e; return *this; } // prefix
Vkrp operator++(int) { Vkrp p(*this); this->p_e--; return p; }
Vkrp operator--(int) { Vkrp p(*this); this->p_e++; return p; }
}; // Vkrp const reverse template iter (pointer)
typedef « Inside the Vt namespace, these typedefs can be used. Most of them allow you to use STL style iterator names. The use of sz_t to mean size_t is only for brevity. public:
typedef size_t sz_t;
typedef sz_t size_type;
typedef Vp iterator;
typedef Vkp const_iterator;
typedef Vrp reverse_iterator;
typedef Vkrp const_reverse_iterator;
members « State in Vt is simple: a vector of elements along with current logical and physical length, plus an allocation source. Iovec v_iov is the allocation when a pool is used. The oddest member is v_e0: it's a null element used to clobber removed elements, using a standard instance created with an empty constructor. (Because logically removed elements are not destroyed with a destructor, we need another way to release resources, if any, without making an element invalid.) private:
cy_pool* v_H; // pool used to allocate space
cy_iovec v_iov; // used only when v_H is non-nil
E* v_e; // first entry/entity/element in vector
sz_t v_n; // size: logically used elements
sz_t v_x; // capacity: physically allocated elements
E v_e0; // default nil when explicit nil not given
// v_empty is default when out-of-bounds array index is used:
static E v_empty; // used when we need empty elem for voob()
getters « You get get all the members, and you can set the value of v_e0 in case the empty constructor doesn't give you a value best suited for downgrading removed values. public: // simple getters (and setters)
cy_pool* vH() const { return v_H; }
E* ve() const { return v_e; }
sz_t vn() const { return v_n; }
sz_t vx() const { return v_x; }
const E& ve0() const { return v_e0; }
void vnil(const E& e0) { v_e0 = e0; } // new value for 'nil'
slices « You can express a subset of Vt using vz(), which simply joins a pointer (in ref form) to start offset and length. public: // slicing
Vcut vz(zp32 p, zn32 n) const {
return Vcut(p, n, *this);
}
Vcut operator()(zp32 p, zn32 n) const {
return Vcut(p, n, *this);
}
printing « This might be sufficient for printing needs. But I put more care into test classes like VtI32x. #ifdef Vt_DUMP_CITE
struct Vq {
Vt const& q_v; Vq(Vt const& v): q_v(v) { }
};
Vq quote() const { return Vq(*this); } // to request dump
void vdump(cy_sink& o) const {
cy_sink_ft(&o, "<Vt me=%lx H=%lx v=%lx n=%u x=%u>",
(long) this, (long) v_H, (long) v_e, v_n, v_x);
cy_sink_ns(&o, "<v_e>");
for (unsigned i = 0; i < v_n; ++i) {
v_e[i].xcite(o);
}
cy_sink_sn(&o, "</v_e>");
Iov raw(v_e, sizeof(E)*v_n);
raw.vshow(o, "raw", /*maxbytes*/ 8192);
cy_sink_unend(&o, "Vt");
}
void vcite(cy_sink& o) const {
cy_sink_f(&o, "<Vt me=%lx H=%lx v=%lx n=%u x=%u/>",
(long) this, (long) v_H, (long) v_e, v_n, v_x);
}
#endif /*Vt_DUMP_CITE*/
alloc « Each time an element array is needed as unconstructed raw space, we ask the pool to allocate memory. Note the iovec returned might be bigger than the request, presumably because pool granularity is coarse. (But pool sample code shows a pool using malloc(); so tests on this page will get exactly what was requested.) Note the use of placement operator new (which returns the pointer arg unchanged) to construct each element in the vector. This idiom is used because you can only call constructors explicitly this way by using operator new. private:
E* _vnew(unsigned n, cy_iovec& iov) { // new[sz]
if (v_H) { // can allocate from a pool?
unsigned minSize = n * sizeof(E);
iov = pool_alloc_do(v_H, minSize);
E* e = (E*) iov.iov_base;
if (e) {
assert(iov.iov_len >= minSize);
// actual size might be bigger:
n = iov.iov_len / sizeof(E);
for (unsigned i = 0; i < n; ++i) {
// use placement operator new on each E:
new (e+i) E; // call empty constructor
}
return e;
}
}
else { // use plain vector operator new
iov.iov_base = 0;
iov.iov_len = 0;
return new E[n];
}
iov.iov_base = 0;
iov.iov_len = 0;
return 0;
}
free « Here we undo whatever happened in _vnew(). Each element is explicitly destroyed by calling the destructor. void _vdelete(E* e, cy_iovec& iov) { // delete [] v
if (v_H) { // was allocated by pool?
assert(iov.iov_base == (void*) e);
unsigned n = iov.iov_len / sizeof(E);
for (unsigned i = 0; i < n; ++i) {
(e+i)->~E(); // destroy each E explicitly
}
pool_free_do(v_H, iov);
}
else { // use plain vector operator delete
delete [] e;
}
}
start « The following init methods are called by constructors. (Note methods beginning with underscore are private api.) void _vup(const Vt& t, sz_t more, E* val) { // 1st new
E* e = t.v_e; sz_t n = t.v_n + more;
if (e && n) v_e = this->_vnew(n, v_iov);
if (v_e) {
v_n = v_x = n;
for (sz_t i = 0; i < t.v_n; ++i)
v_e[i] = e[i];
if (more && val) {
for (sz_t j = t.v_n; j < n; j++)
v_e[j] = *val;
}
}
}
void _vup(const E* e, const E* x) { // first new
// alloc+copy (x-e) elems from e
if (x > e) {
sz_t n = x - e;
if (e && n)
v_e = this->_vnew(n, v_iov);
if (v_e) {
v_n = v_x = n;
for (sz_t i = 0; i < n; ++i)
v_e[i] = e[i];
}
}
}
capacity « Here's how you reserve minimum physical length: bool _vreserve(sz_t minCap) { // up capacity to min; keep old elems
if (cy_unlikely(v_x >= minCap)) return true; // already big enough
cy_iovec newIov;
E* e = this->_vnew(minCap, newIov); // minCap != 0
if (e) {
if (v_e) { // anything to copy?
for (sz_t i = 0; i < v_n; ++i)
e[i] = v_e[i]; // copy old e's
this->_vdelete(v_e, v_iov); // free old space
}
v_e = e; // adopt new space
v_iov = newIov;
v_x = minCap; // new capacity matches requested minimum
return true; // successful upward resize
}
else
cy_logf(0, "Vt::_vreserve() v_e=nil");
return false; // allocation failed; failed upward resize
}
/// \brief _vgrow() makes space for more elems needed by vinsert()
E* _vgrow(const E* e, sz_t more) { // returns E* at 1st new slot
if (cy_unlikely(!more)) return (E*) e; // noop
sz_t i = e - v_e; // save offset so we can re-establish post realloc
sz_t n = v_n; // starting length
if (i > n) i = n; // disallow reference past previous vector end
sz_t plus = n + more; // future length after we add more to length
if (this->_vreserve(plus)) { // assured min capacity for new len?
e = v_e + i; // need to refresh in case vector was reallocated
if (n > i) { // elems follow insertion point? need to move them?
sz_t tail = n - i; // elem count at (old) tail end to be moved
E* x = v_e + n; // one past the old end (one beyond 1st to move)
E* t = x - tail; // leftmost of the N=tail elems to be moved
// copy to self avoids self clobber only if we go right to left:
while (--x >= t) // have not passed leftmost? one more e to move?
x[more] = *x; // distance to move is total elem count added
}
v_n = plus; // new elems now count as added to existing length
return (E*) e; // first new elem slot where insert gets written
}
return (E*) 0; // nil means failure to provide new space
}
This internal _vmin() method reserves minimum physical capacity without saving any old elements. This is used when assignment replaces all old elements anyway. bool _vmin(sz_t minCap) { // up capacity to minCap; nocopy old elems
if (cy_unlikely(v_x >= minCap)) return true; // already big enough
cy_iovec newIov;
E* e = this->_vnew(minCap, newIov); // minCap cannot be zero here
if (e) {
if (v_e) { // anything to delete?
// do NOT copy old content
this->_vdelete(v_e, v_iov); // free old space
}
v_e = e; // adopt new space
v_iov = newIov;
v_x = minCap; // new capacity matches requested minimum
return true; // successful upward resize
}
else
cy_logf(0, "Vt::_vmin() v_e=nil");
return false; // allocation failed; failed upward resize
}
internal assignment « This internal assignment copies a sequence of input elements while discarding all old members. Vt& _vassign(const E* e, const E* x, sz_t more) { // common assign code
sz_t num = (x > e)? (x - e) : 0; // backwards (ie e > x) not supported
if (this->_vmin(num+more)) { // assured minCap is now num+more?
v_n = num; // new size is requested range of size (x - e)
for (sz_t i = 0; i < num; ++i) // copy from source vector
v_e[i] = e[i];
}
return *this;
}
internal remove « Here's how you remove one or N vector elements. Note the use of diagrams to show cases for reference when reviewing the code. (I find it hard to write code without such figures.) Ve _verase(sz_t i, const E* e0=0) { // zap v_e[i]; v_e[last]=*e0
sz_t last = v_n - 1; // index of last elem (if v_n is positive)
if (cy_unlikely(!v_e || !v_n || i > last)) // now empty or no target
return Ve(v_e + v_n); // almost means "after erased elem"
--v_n; // need to reduce size by one here, any way you cut it
while (i < last) { // need to move another elem backward by one?
v_e[i] = v_e[i+1]; // assign successor to predecessor
++i;
}
if (!e0)
e0 = &v_e0;
v_e[last] = *e0; // nil out last slot if nil is supplied
return Ve(v_e + i); // after erased elem (where erased elem was)
}
// here's a figure you can study while working through _verase() cases:
// +---+---+---+---+---+---+---+---+---+---+---+
// | a | b | c | d | e | f | g | h | i | j | k | z v_n=12
// +---+---+---+---+-|-+---+---+-x-+---+-x-+---+ x x
// verase() examples |<--- 3 --->| cut e,f,g dn=3 dz=&v[i]
// |<------- 5 ------->| cut e,f,g,h,i dn=5 dz=&v[g]
// |<----------- 7 ----------->| cut e,f,g,h,i,j,k
Ve _verase(E* e, const E* x, const E* e0=0) {
E* a = v_e; E* z = a + v_n; // start+end (1 after last) of my vector
if (e < a) e = a; // start should not go before my start
if (x > z) x = z; // end should not go beyond my end
if (!a || e >= x) return Ve(x); // no intersection: noop
sz_t dn = x - e; // requested reduction in v_n is dn: count to remove
if (z > x) { // do any kept e's follow erasure? need to move forward?
E* dz = z - dn; // the new end (one past last) when dn are removed
for ( ; e < dz; ++e) // for each elem to move foward
*e = e[dn]; // distance to move is the total count removed
}
v_n -= dn; // decrease length by removed elems
if (!e0)
e0 = &v_e0;
for ( ; e < z; ++e) // for each newly free slot after new last e
*e = *e0; // replace newly unused slot with nominal "nil" value
return Ve(x); // element after last element erased
}
iterators « The following methods return iterators. Note end always returns an address one beyond the last element to be visited by the iterator starting at the opposite end. public:
Vp vbegin() { return Vp(v_e); } // 1st
Vkp vbegin() const { return Vkp(v_e); } // 1st
Vrp vrbegin() { return Vrp(v_e + v_n - 1); } // last
Vkrp vrbegin() const { return Vkrp(v_e + v_n - 1); }
Vp vend() { return Vp(v_e + v_n); } // post last
Vkp vend() const { return Vkp(v_e + v_n); } // post last
Vrp vrend() { return Vrp(v_e - 1); } // pre 1st
Vkp vrend() const { return Vkrp(v_e - 1); } // pre 1st
(Some folks note C systems do not guarantee an address exists, even logically, at a position one before a memory allocation. My response is simple: if a platform does not define that address, then screw that platform.) out of bounds « If you try to access an element out of bounds (after the last used element), the following element is returned instead of, say, crashing. You should enable the assert when testing. E& voob(sz_t i) { // log on out-of-bounds index, return empty
cy_logf(0, "Vt oob! index=%d size=%d\n", i, v_n);
// assert(i < v_n);
return v_empty; // std empty used when voob() occurs
}
//void vswap(Vt& t) { // exchange all fields with another Vt
// cy_pool* h = v_H; v_H = t.v_H; t.v_H = h;
// E* e = v_e; v_e = t.v_e; t.v_e = e;
// sz_t n = v_n; v_n = t.v_n; t.v_n = n;
// sz_t x = v_x; v_x = t.v_x; t.v_x = x;
//}
clear « Clearing a vector removes all members. Presumably you want all resources back too, so merely marking logical size as zero is not enough. // void vclear() { Vt empty(v_H); this->vswap(empty); }
void vclear() { // same as ~Vt()
if (v_e) {
// delete [] v_e;
this->_vdelete(v_e, v_iov);
v_e = 0;
}
v_n = 0; v_x = 0; // save the v_H as-is
}
destroy « The destructor does the same thing as vclear() (and could use clear) but the vector is unusable afterward. If Vt had a magic number in a field, this is where you would write an invalid value like 0xdeadbeef. ~Vt() {
if (v_e) {
// delete [] v_e;
this->_vdelete(v_e, v_iov);
v_e = 0;
}
v_H = 0; v_n = 0; v_x = 0;
}
construct « New vectors are constructed like this. Note the use of a global cy_pool instance when no pool is specified, causing all space to be allocated on the pool using malloc() and free(). Vt() : v_H(cy_global_heappool()), v_e(0), v_n(0), v_x(0) { }
Vt(cy_pool* p) : v_H(p), v_e(0), v_n(0), v_x(0) { }
Vt(cy_pool* p, const E& e)
: v_H(p), v_e(0), v_n(0), v_x(0), v_e0(e) { }
Vt(const Vt& t, sz_t more)
: v_H(cy_global_heappool()), v_e(0), v_n(0), v_x(0) {
this->_vup(t, more, (E*) 0); // first new
}
Vt(const Vt& t, sz_t more, E& val)
: v_H(cy_global_heappool()), v_e(0), v_n(0), v_x(0) {
this->_vup(t, more, &val); // first new
}
explicit Vt(const Vt& t)
: v_H(cy_global_heappool()), v_e(0), v_n(0), v_x(0) {
E* e = t.v_e; sz_t n = t.v_n;
if (e && n) v_e = this->_vnew(n, v_iov);
if (v_e) {
v_n = v_x = n; // size & capacity exactly t.vsize()
for (sz_t i = 0; i < n; ++i)
v_e[i] = e[i];
}
}
explicit Vt(sz_t num)
: v_H(cy_global_heappool()), v_e(0), v_n(0), v_x(0) {
if (num) v_e = this->_vnew(num, v_iov);
if (v_e) v_n = v_x = num; // size & capacity exactly same
}
Vt(sz_t num, const E& e)
: v_H(cy_global_heappool()), v_e(0), v_n(0), v_x(0) {
if (num) v_e = this->_vnew(num, v_iov);
if (v_e) {
v_n = v_x = num; // size and capacity exactly the same
for (sz_t i = 0; i < num; ++i) v_e[i] = e;
}
}
Vt(const E* start, const E* end)
: v_H(cy_global_heappool()), v_e(0), v_n(0), v_x(0) {
this->_vup(start, end); // first new
}
index « This operator supports array access, returning a mutable element reference when the vector is not const: E& operator[]( sz_t i ) { return (i<v_n)? v_e[i] :voob(i); }
const E& operator[](sz_t i) const {
return (i < v_n)? v_e[i] : this->voob(i);
}
compare « It appears I meant to support comparison one day: bool operator==(const Vt& x) const;
bool operator!=(const Vt& x) const;
bool operator< (const Vt& x) const;
bool operator> (const Vt& x) const;
bool operator<=(const Vt& x) const;
bool operator>=(const Vt& x) const;
modify « Assignment replaces vector content with new values. Vt& vassign( sz_t num, const E& val ) {
if (this->_vmin(num)) { // capacity at least min of num?
v_n = num;
for (sz_t i = 0; i < num; ++i)
v_e[i] = val; // every used slot in first vsize()
}
return *this;
}
Vt& operator=(const Vt& t) { // assign t members to this
this->_vassign(t.v_e, t.v_e + t.v_n, 0);
}
Vt& vassign(Ve a, Ve b) {
return _vassign(a.kpe(), b.kpe(), 0);
}
elements « You can get the back element or the front, or any at a given index. If the element doesn't exists, you get whatever voob() returns for out-of-bounds. E& vat( sz_t i ) { return (i < v_n)? v_e[i] : voob(i); }
const E& vat(sz_t i) const {
return (i < v_n)? v_e[i] : voob(i);
}
E& vback() { return (v_e&&v_n)? v_e[v_n-1] : voob(0); }
const E& vback() const {
return (v_e&&v_n)? v_e[v_n-1] : voob(0);
}
const E& vfront() const {
return (v_e && v_n)? v_e[0] : voob(0);
}
E& vfront() { return (v_e && v_n)? v_e[0] : voob(0); }
size_t vcapacity() const { return v_x; }
population « These methods query logical vector size: sz_t vsize() const { return v_n; } // number of elems in vector
bool vempty() const { return !v_e || !v_n; }
remove « You can remove vector elements many ways: void verase(sz_t i) { this->_verase(i, (const E*)0); }
void verase(sz_t i, const E& val) { this->_verase(i, &val); }
Ve verase(Ve e) { // return pos after erased elem e
sz_t i = e.kpe() - v_e; return this->_verase(i, (const E*)0);
}
Ve verase(Ve e, const E& zero) {
sz_t i = e.kpe() - v_e;
return this->_verase(i, &zero); // pos after erased e
}
Ve verase(Ve e, Ve x) { // pos after last erased e
return this->_verase(e.pe(), x.pe(), (const E*)0);
}
Ve verase(Ve e, Ve x, const E& zero) {
return this->_verase(e.pe(), x.pe(), &zero);
}
Ve verase(E* e) { // return pos after erased elem e
sz_t i = e - v_e; return this->_verase(i, (const E*)0);
}
Ve verase(E* e, const E& zero) {
if (e >= v_e) {
sz_t i = e - v_e;
return this->_verase(i, &zero); // pos after erased e
}
return Ve(v_e);
}
Ve verase(E* e, const E* x) { // pos after last erased e
return this->_verase(e, x, (const E*)0);
}
Ve verase(E* e, const E* x, const E& zero) {
return this->_verase(e, x, &zero);
}
intersection « The following methods say whether two element sequences overlap physically in memory, using the iovecs involved to check for address overlaps. This appears in the api so you can avoid inserting content in a vector when source and destination overlap—don't do that. Vt doesn't support self insert with overlap. bool vintersects(Ve e, Ve x) const {
Iov us(v_e, v_x * sizeof(E));
sz_t themLen = x.kpe() - e.kpe();
Iov them(e.kpe(), themLen * sizeof(E));
return us.voverlaps(them); // bad idea for inserts if true
}
bool vintersects(const E* e, const E* x) const {
Iov us(v_e, v_x * sizeof(E));
sz_t themLen = x - e;
Iov them(e, themLen * sizeof(E));
return us.voverlaps(them); // bad idea for inserts if true
}
insert « Here's how you add new elements via insert. When you add a single element, I make a local copy first to avoid pathological results when overlap occurs. (But if you insert a sequence of elements, you're on your own when it comes to overlap.) Ve vinsert(const E* e, const E& val) {
if (v_x > v_n) { // no grow is needed?
E* slot = this->_vgrow(e, /*more*/ 1);
if (slot) *slot = val;
return Ve(slot); // where insertion happened
}
E t = val; // tmp copy in case e is self-referential insert
E* slot = this->_vgrow(e, /*more*/ 1);
if (slot) *slot = t;
return Ve(slot); // where insert happened (may be new)
}
void vinsert(const E* e, sz_t num, const E& val) {
E tmp = val; // avoid any self referential insert issue
E* slot = this->_vgrow(e, /*more*/ num);
if (slot) {
for (sz_t i = 0; i < num; ++i)
slot[i] = tmp;
}
}
void vinsert(const E* e, const E* start, const E* end) {
//if (this->vintersects(start, end)) { // self insertion?
// cy_logf(0, "Vt::vinsert() self insertion\n");
// return; // do nothing
//}
sz_t more = (end > start)? (end - start) : 0;
if (more) {
E* slot = this->_vgrow(e, more);
if (slot) {
for (sz_t i = 0; i < more; ++i)
slot[i] = start[i];
}
}
}
You can push and pop the back (the end easiest to change efficiently). The idea of max size in STL vectors is given barely serviceable support: just a plausible number. sz_t vmax_size() { return (0x7FFFFFF / sizeof(E)); }
void vpop_back() { if (v_n) { --v_n; v_e[v_n] = v_e0; } }
void vpush_back(E const& e) {
if (v_x > v_n) { // no grow is needed?
v_e[v_n] = e; // new last slot
++v_n; // count new last slot
return; // skip the step of making a temp copy of e
}
E t = e; // tmp copy in case e is self-referential insert
if (_vreserve(v_n + 3)) { // room for at least one more e?
v_e[v_n] = t; // new last slot
++v_n; // count new last slot
}
}
sizing « You can reserve physical capacity ahead of use, or change the vector size to any value you like. void vreserve(sz_t min) { this->_vreserve(min); }
void vresize(sz_t sz) { // change the size of the vector
if (_vreserve(sz)) { // min v_x >= sz now? old slots kept?
v_n = sz;
}
}
If you resize a vector smaller and want removed elements to be clobbered with some value (instead of sitting unused) you can use this variant to write overwrite elements. Note this should still leave elements valid and constructed to satisfy an always constructed invariant. void vresize( sz_t num, const E& val) {
if (num == v_n) return; // noop
if (_vreserve(num)) { // min v_x >= num now? old slots kept?
if (num > v_n) { // need to assign val to some new slots?
for (sz_t i = v_n; i < num; ++i)
v_e[i] = val; // init each new slot at end
}
v_n = num;
}
}
}; // class Vt
///// ----- v_empty ----- /////
template <typename E> E Vt<E>::v_empty;
streaming « You can use operator<<() to print. Note the C-based out stream api appears on sink. #ifdef Vt_DUMP_CITE
template <typename E>
cy_sink& operator<<(cy_sink& o, Vt<E> const& x) {
x.vdump(o); return o;
}
template <typename E>
cy_sink& operator<<(cy_sink& o, Cite< Vt<E> > const& x) {
x.c_t.vcite(o); return o;
}
#endif /*Vt_DUMP_CITE*/
test « I toss in a casual test method for many new classes. ///// ----- test ----- /////
int Vt_test_main(int argc, char * const* argv);
I32x « The following example 32-bit integer class has an extra 32-bit tag field (for aggressive dynamic typing), and knows how to print itself. A vector class below uses this integer type. ///// ----- I32x ----- /////
struct I32x { // tagged i32 example
i32 x_val;
u32 x_tag; enum { k_xtag = 0x49333278 }; /* 'I32x' */
I32x() : x_val(-1), x_tag(k_xtag) { }
I32x(i32 v) : x_val(v), x_tag(k_xtag) { }
I32x& operator=(i32 v) { // assert tag and assign val
assert(k_xtag == x_tag); x_val = v; return *this; }
operator i32() const {
assert(k_xtag == x_tag); return x_val;
}
struct Xq { I32x const& q_x; Xq(I32x const& x): q_x(x) { } };
Xq quote() const { return Xq(*this); } // to request dump
void xdump(cy_sink& o) const;
void xcite(cy_sink& o) const;
};
inline cy_sink& operator<<(cy_sink& o, I32x const& x) {
x.xdump(o); return o;
}
inline cy_sink& operator<<(cy_sink& o, I32x::Xq const& x) {
x.q_x.xdump(o); return o;
}
inline cy_sink& operator<<(cy_sink& o, Cite<I32x> const& x) {
x.c_t.xcite(o); return o;
}
Code for dump and cite is minimal and simple: ///// ----- I32x ----- /////
void I32x::xdump(cy_sink& o) const {
assert(k_xtag == x_tag);
cy_sink_f(&o, "<x v=%d/>", x_val);
}
void I32x::xcite(cy_sink& o) const {
assert(k_xtag == x_tag);
cy_sink_f(&o, "%d, ", x_val);
}
VtI32x « The following example vector class contains instances of example 32-bit integer class I32x shown above. The main purpose of this VtI32x wrapper class is to provide printing support so the vector inside can be examined. ///// ----- VtI32x ----- /////
struct VtI32x { // tagged i32 example vector
Vt<I32x> v_vt; // vector printed knowing elems are I32x
bool v_verbose; // if true, include more in dump (raw hex)
VtI32x() : v_vt(), v_verbose(false) { } // empty vector
VtI32x(cy_pool* p) : v_vt(p), v_verbose(false) { } // empty
VtI32x(cy_pool* p, I32x const& e)
: v_vt(p, e), v_verbose(false) { }
struct Vq {
VtI32x const& q_v; Vq(VtI32x const& v): q_v(v) { }
};
Vq quote() const { return Vq(*this); } // to request dump
void vdump(cy_sink& o) const; void vcite(cy_sink& o) const;
};
inline VtI32x& operator<<(VtI32x& v, I32x& x) {
v.v_vt.vpush_back(x); return v; }
inline cy_sink& operator<<(cy_sink& o, VtI32x const& x) {
x.vdump(o); return o; }
inline cy_sink& operator<<(cy_sink& o, VtI32x::Vq const& x) {
x.q_v.vdump(o); return o; }
inline cy_sink& operator<<(cy_sink& o, Cite<VtI32x> const& x) {
x.c_t.vcite(o); return o; }
The most interesting dump behavior occurs when v_verbose is true: then hex for the vector is dumped, including the "unused" part of the vector at the end. ///// ----- VtI32x ----- /////
void VtI32x::VtI32x::vdump(cy_sink& o) const {
Vt<I32x> const& v = v_vt;
const I32x* ve = v.ve();
cy_pool* vH = v.vH();
unsigned vn = v.vn();
unsigned vx = v.vx();
cy_sink_ft(&o, "<Vt me=%lx H=%lx v=%lx n=%u x=%u>",
(long) &v, (long) vH, (long) ve, vn, vx);
if (v_verbose) {
cy_sink_nst(&o, "<v_e>");
for (unsigned i = 0; i < vn; ++i) {
if (i && 0 == (i % 10)) // another ten values?
cy_sink_n(&o);
o << cy_cite(ve[i]);
}
cy_sink_us(&o, "</v_e>");
if (vn) {
cy_sink_n(&o);
Iov raw(ve, sizeof(I32x)*vn);
raw.vshow(o, "raw", /*maxbytes*/ 8192);
}
if (vx > vn) {
cy_sink_n(&o);
unsigned more = vx - vn;
Iov post(ve + vn, sizeof(I32x)*more);
post.vshow(o, "post", /*maxbytes*/ 8192);
}
}
else if (vn) {
cy_sink_n(&o);
for (unsigned i = 0; i < vn; ++i) {
if (i && 0 == (i % 10)) // another ten values?
cy_sink_n(&o);
ve[i].xcite(o);
}
}
cy_sink_unend(&o, "Vt");
}
void VtI32x::vcite(cy_sink& o) const {
Vt<I32x> const& v = v_vt;
cy_sink_f(&o, "<Vt me=%lx H=%lx v=%lx n=%u x=%u/>",
(long) &v, (long) v.vH(), (long) v.ve(), v.vn(), v.vx());
}
I32xpv « Note I32xpv below is nearly identical to VtI32x above. When you look closely, you'll see VtI32x has an inline instance of Vt<I32x> while I32xpv has a reference to an vector elsewhere. This was an easy way to create a second wrapper for a single vector, so you could set the verbose flag differently in two places, then choose which way to print (verbose or not) by choosing either the I32xpv or VtI32x instance to print. ///// ----- I32xpv ----- /////
struct I32xpv { // tagged i32 example vector
Vt<I32x>& v_vt; // vector printed knowing elems are I32x
bool v_verbose; // if true, include more in dump (raw hex)
I32xpv(Vt<I32x>& vt) : v_vt(vt), v_verbose(false) { }
struct Vq {
I32xpv const& q_v; Vq(I32xpv const& v): q_v(v) { }
};
Vq quote() const { return Vq(*this); } // to request dump
void vdump(cy_sink& o) const; void vcite(cy_sink& o) const;
};
inline I32xpv& operator<<(I32xpv& v, I32x& x) {
v.v_vt.vpush_back(x); return v; }
inline cy_sink& operator<<(cy_sink& o, I32xpv const& x) {
x.vdump(o); return o; }
inline cy_sink& operator<<(cy_sink& o, I32xpv::Vq const& x) {
x.q_v.vdump(o); return o; }
inline cy_sink& operator<<(cy_sink& o, Cite<I32xpv> const& x) {
x.c_t.vcite(o); return o; }
}; // namespace cy
Since printing code for I32xpv is identical to VtI32x, it's not shown here.
main
Okay, let's use those example classes above to use vector methods and print the results afterward. preamble « The main() for testing a cy class looks similar each time. Here's how we initialize a pool-based pool, and set up an out stream writing to stdout: int g_Vt_test_errs = 0;
int Vt_test_main(int argc, char * const* argv)
{
char buf[ 2048 + 4 ];
cy_fdsink sout;
cy_iovec_t bufiov;
cy_sink& o = sout.f_sink;
bufiov.iov_base = buf; bufiov.iov_len = 2048;
cy_fdsink_ctor(&sout, bufiov, STDOUT_FILENO);
cy_pool* pool = cy_global_heappool();
{ // scope for VtI32x v(pool, empty_e0);
// body of test goes here
}
o << cy_endl << "pool after ~Vt():" << cy_endl;
o << *pool << cy_endl;
o << cy_now;
return g_Vt_test_errs;
}
Code above creates a sink instance named o (for out) writing to stdout, and gets a global pool pool instance named pool. The lat line of output should look as follows to reveal all vector allocated space has been freed: pool after ~Vt():
<heappool iovs=0 allocs=6 frees=6 total=0/>
Those statistics mean all the allocations were freed, resulting in zero iovs now outstanding for a total of zero bytes now allocated. If the vector leaks, we'll see space sitting around when all the vectors are gone. body « The rest of this sample code shows an assortment of vector calls and printing code in the body of the test. o << cy_endl << "<Vt_test_main>" << cy_endl;
o << "\n# before Vt:" << cy_endl << *pool << cy_endl;
I32x empty_e0(0xeeaaeeaa);
VtI32x v(pool, empty_e0);
v.v_verbose = true;
o << v << cy_endl;
o << "\n# after Vt:" << cy_endl << *pool << cy_endl;
Code above says to use an instance of I32x with value 0xeeaaeeaa by default when clobbering removed elements. Output below shows nothing has yet been allocated in vector v: <Vt_test_main>
# before Vt:
<heappool iovs=0 allocs=0 frees=0 total=0/>
<Vt me=bffff9e4 H=2c3300 v=0 n=0 x=0>
<v_e></v_e>
</Vt>
# after Vt:
<heappool iovs=0 allocs=0 frees=0 total=0/>
I32x x1(1); v << x1;
o << "\n# append 1:" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
After appending a one value, 1, the vector looks like this: # append 1:
<Vt me=bffff9e4 H=2c3300 v=300520 n=1 x=3>
<v_e>1, </v_e>
<raw p=0x300520 n=8 crc=0xdb87fa7e:8>
00000: 01 00 00 00 78 32 33 49 ; ....x23I
</raw>
<post p=0x300528 n=16 crc=0xb4987350:16>
00000: ff ff ff ff 78 32 33 49 ff ff ff ff 78 32 33 49 ; ....x23I....x23I
</post>
</Vt>
<heappool iovs=0 allocs=1 frees=0 total=24/>
Integers appear in little endian format, including the tag in each I32x instance. Two extra instances contain -1 (0xffffffff) because that's what the I32x empty constructor uses. I32x x2(2); v << x2;
o << "\n# append 2:" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
After appending appending a second value of 2 we get: # append 2:
<Vt me=bffff9e4 H=2c3300 v=300520 n=2 x=3>
<v_e>1, 2, </v_e>
<raw p=0x300520 n=16 crc=0xd14c7180:16>
00000: 01 00 00 00 78 32 33 49 02 00 00 00 78 32 33 49 ; ....x23I....x23I
</raw>
<post p=0x300530 n=8 crc=0x8df0da76:8>
00000: ff ff ff ff 78 32 33 49 ; ....x23I
</post>
</Vt>
<heappool iovs=0 allocs=1 frees=0 total=24/>
... which merely shows an another slot was used and filled with the new element. o << "\n# v[1]:" << cy_endl << v.v_vt.vat(1) << cy_endl;
o << *pool << cy_endl;
If we access the element at zero-based index 1 we see: # v[1]:
<x v=2/>
<heappool iovs=0 allocs=1 frees=0 total=24/>
o << "\n# v[10]:" << cy_endl << v.v_vt.vat(10) << cy_endl;
o << *pool << cy_endl;
But if we read past the end at index 10, we get out of bounds: Vt oob! index=10 size=2
# v[10]:
<x v=-1/>
<heappool iovs=0 allocs=1 frees=0 total=24/>
I32x x3(3); v << x3;
o << "\n# append 3:" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
Appending another element uses the last of existing capacity: # append 3:
<Vt me=bffff9e4 H=2c3300 v=300520 n=3 x=3>
<v_e>1, 2, 3, </v_e>
<raw p=0x300520 n=24 crc=0x4c893fb8:24>
00000: 01 00 00 00 78 32 33 49 02 00 00 00 78 32 33 49 ; ....x23I....x23I
00010: 03 00 00 00 78 32 33 49 ; ....x23I
</raw>
</Vt>
<heappool iovs=0 allocs=1 frees=0 total=24/>
v.v_verbose = false;
I32x x4(4); v << x4;
o << "\n# append 4:" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
v.v_verbose = true;
Here we turn off verbose printing and add a fourth element. Now when we print, we see address v=300520 changed to v=3005d0 because a new vector was allocated: # append 4:
<Vt me=bffff9e4 H=2c3300 v=3005d0 n=4 x=6>
1, 2, 3, 4,
</Vt>
<heappool iovs=0 allocs=2 frees=1 total=48/>
unsigned j = 0;
for ( ; j < 4; ++j) {
I32x x2(5 + j); v << x2;
}
o << "\n# append four more:" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
The loop above appends four more elements, which grows the vector again: # append four more:
<Vt me=bffff9e4 H=2c3300 v=300600 n=8 x=9>
<v_e>1, 2, 3, 4, 5, 6, 7, 8, </v_e>
<raw p=0x300600 n=64 crc=0xfe04677:64>
00000: 01 00 00 00 78 32 33 49 02 00 00 00 78 32 33 49 ; ....x23I....x23I
00010: 03 00 00 00 78 32 33 49 04 00 00 00 78 32 33 49 ; ....x23I....x23I
00020: 05 00 00 00 78 32 33 49 06 00 00 00 78 32 33 49 ; ....x23I....x23I
00030: 07 00 00 00 78 32 33 49 08 00 00 00 78 32 33 49 ; ....x23I....x23I
</raw>
<post p=0x300640 n=8 crc=0x8df0da76:8>
00000: ff ff ff ff 78 32 33 49 ; ....x23I
</post>
</Vt>
<heappool iovs=0 allocs=3 frees=2 total=72/>
o << "\n# forward iterate:" << cy_endl;
Vt<I32x>::Vp kp = v.v_vt.vbegin();
for ( ; kp != v.v_vt.vend(); ++kp) {
const I32x& val = *kp;
o << cy_cite(val);
}
o << cy_endl;
The loop above iterates over elements, citing each: # forward iterate:
1, 2, 3, 4, 5, 6, 7, 8,
o << "\n# backward iterate:" << cy_endl;
Vt<I32x>::Vrp rp = v.v_vt.vrbegin();
for ( ; rp != v.v_vt.vrend(); ++rp) {
const I32x& val = *rp;
o << cy_cite(val);
}
o << cy_endl;
A similar loop with a reverse iterator prints: # backward iterate:
8, 7, 6, 5, 4, 3, 2, 1,
I32x back = v.v_vt.vback();
v.v_vt.vpop_back();
o << "\n# back then pop_back:" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
After popping the last vector member, we see 0xeeaaeeaa from v_e0 was use to set the old value. # back then pop_back:
<Vt me=bffff9e4 H=2c3300 v=300600 n=7 x=9>
<v_e>1, 2, 3, 4, 5, 6, 7, </v_e>
<raw p=0x300600 n=56 crc=0xba5db3f:56>
00000: 01 00 00 00 78 32 33 49 02 00 00 00 78 32 33 49 ; ....x23I....x23I
00010: 03 00 00 00 78 32 33 49 04 00 00 00 78 32 33 49 ; ....x23I....x23I
00020: 05 00 00 00 78 32 33 49 06 00 00 00 78 32 33 49 ; ....x23I....x23I
00030: 07 00 00 00 78 32 33 49 ; ....x23I
</raw>
<post p=0x300638 n=16 crc=0x3138f82a:16>
00000: aa ee aa ee 78 32 33 49 ff ff ff ff 78 32 33 49 ; ....x23I....x23I
</post>
</Vt>
<heappool iovs=0 allocs=3 frees=2 total=72/>
Vt<I32x>::Ve p = v.v_vt.vbegin() + 4;
I32x x44(44);
v.v_vt.vinsert(p, x44);
o << "\n# vinsert(4, x44):" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
Code above inserts 0x44 after the fourth member: # vinsert(4, x44):
<Vt me=bffff9e4 H=2c3300 v=300600 n=8 x=9>
<v_e>1, 2, 3, 4, 44, 5, 6, 7, </v_e>
<raw p=0x300600 n=64 crc=0x8ca804e:64>
00000: 01 00 00 00 78 32 33 49 02 00 00 00 78 32 33 49 ; ....x23I....x23I
00010: 03 00 00 00 78 32 33 49 04 00 00 00 78 32 33 49 ; ....x23I....x23I
00020: 2c 00 00 00 78 32 33 49 05 00 00 00 78 32 33 49 ; ,...x23I....x23I
00030: 06 00 00 00 78 32 33 49 07 00 00 00 78 32 33 49 ; ....x23I....x23I
</raw>
<post p=0x300640 n=8 crc=0x8df0da76:8>
00000: ff ff ff ff 78 32 33 49 ; ....x23I
</post>
</Vt>
<heappool iovs=0 allocs=3 frees=2 total=72/>
v.v_vt.verase(1);
o << "\n# erase(1):" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
After erasing the member at zero-based index 1, we see the old second member is now absent: # erase(1):
<Vt me=bffff9e4 H=2c3300 v=300600 n=7 x=9>
<v_e>1, 3, 4, 44, 5, 6, 7, </v_e>
<raw p=0x300600 n=56 crc=0x158f2d32:56>
00000: 01 00 00 00 78 32 33 49 03 00 00 00 78 32 33 49 ; ....x23I....x23I
00010: 04 00 00 00 78 32 33 49 2c 00 00 00 78 32 33 49 ; ....x23I,...x23I
00020: 05 00 00 00 78 32 33 49 06 00 00 00 78 32 33 49 ; ....x23I....x23I
00030: 07 00 00 00 78 32 33 49 ; ....x23I
</raw>
<post p=0x300638 n=16 crc=0x3138f82a:16>
00000: aa ee aa ee 78 32 33 49 ff ff ff ff 78 32 33 49 ; ....x23I....x23I
</post>
</Vt>
<heappool iovs=0 allocs=3 frees=2 total=72/>
Vt<I32x> vag(v.v_vt.ve() + 1, v.v_vt.ve() + 5);
I32xpv vagain(vag);
o << "\n# vagain:" << cy_endl << vagain << cy_endl;
o << *pool << cy_endl;
Here we construct a new vector using a subsequence of the old one as source elements to be copied. Note the use of I32xpv to refer to that vector indirectly when printing: # vagain:
<Vt me=bffffa08 H=2c3300 v=300520 n=4 x=4>
3, 4, 44, 5,
</Vt>
<heappool iovs=0 allocs=4 frees=2 total=104/>
kp = vag.vbegin();
for ( ; kp != vag.vend(); ++kp) {
I32x& val = *kp;
val = 770 + val;
}
o << "\n# add 770 to all - vagain:" << cy_endl << vagain << cy_endl;
o << *pool << cy_endl;
The loop above uses a forward iterator to visit every element in new vector vag while adding 770 to each element, which shows the iterator provides mutable member access: # add 770 to all - vagain:
<Vt me=bffffa08 H=2c3300 v=300520 n=4 x=4>
773, 774, 814, 775,
</Vt>
<heappool iovs=0 allocs=4 frees=2 total=104/>
I32x x99(99);
vag.vinsert(vag.ve() + 5, 3, x99);
vagain.v_verbose = true;
o << "\n# vinsert(vag, 5, x99):" << cy_endl << vagain << cy_endl;
o << *pool << cy_endl;
Code above says to insert 99 three times after the first five existing members. Because the vector only had four members before, the insert occurs at end of vector: # vinsert(vag, 5, x99):
<Vt me=bffffa08 H=2c3300 v=300650 n=7 x=7>
<v_e>773, 774, 814, 775, 99, 99, 99, </v_e>
<raw p=0x300650 n=56 crc=0x17923b2e:56>
00000: 05 03 00 00 78 32 33 49 06 03 00 00 78 32 33 49 ; ....x23I....x23I
00010: 2e 03 00 00 78 32 33 49 07 03 00 00 78 32 33 49 ; ....x23I....x23I
00020: 63 00 00 00 78 32 33 49 63 00 00 00 78 32 33 49 ; c...x23Ic...x23I
00030: 63 00 00 00 78 32 33 49 ; c...x23I
</raw>
</Vt>
<heappool iovs=0 allocs=5 frees=3 total=128/>
v.v_vt.vinsert(v.v_vt.ve() + 2, vag.ve() + 1, vag.vend() - 2);
o << "\n# v_t.vinsert(...):" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
Above we insert a subset of new vag into old vector v starting after v's second member. Copied elems in vag start one after the first, ending two before the end. Afterward v looks like this: # v_t.vinsert(...):
<Vt me=bffff9e4 H=2c3300 v=300690 n=11 x=11>
<v_e>1, 3, 774, 814, 775, 99, 4, 44, 5, 6,
7, </v_e>
<raw p=0x300690 n=88 crc=0xa9457f0e:88>
00000: 01 00 00 00 78 32 33 49 03 00 00 00 78 32 33 49 ; ....x23I....x23I
00010: 06 03 00 00 78 32 33 49 2e 03 00 00 78 32 33 49 ; ....x23I....x23I
00020: 07 03 00 00 78 32 33 49 63 00 00 00 78 32 33 49 ; ....x23Ic...x23I
00030: 04 00 00 00 78 32 33 49 2c 00 00 00 78 32 33 49 ; ....x23I,...x23I
00040: 05 00 00 00 78 32 33 49 06 00 00 00 78 32 33 49 ; ....x23I....x23I
00050: 07 00 00 00 78 32 33 49 ; ....x23I
</raw>
</Vt>
<heappool iovs=0 allocs=6 frees=4 total=144/>
I32x bcde(0xbbccddee);
v.v_vt.verase(v.v_vt.ve() + 3, v.v_vt.vend() - 2, bcde);
o << "\n# v_t.verase(...):" << cy_endl << v << cy_endl;
o << *pool << cy_endl;
Finally, we erase all members after the third and before the last two, using 0xbbccddee to clobber removed elements: # v_t.verase(...):
<Vt me=bffff9e4 H=2c3300 v=300690 n=5 x=11>
<v_e>1, 3, 774, 6, 7, </v_e>
<raw p=0x300690 n=40 crc=0x47822d98:40>
00000: 01 00 00 00 78 32 33 49 03 00 00 00 78 32 33 49 ; ....x23I....x23I
00010: 06 03 00 00 78 32 33 49 06 00 00 00 78 32 33 49 ; ....x23I....x23I
00020: 07 00 00 00 78 32 33 49 ; ....x23I
</raw>
<post p=0x3006b8 n=48 crc=0xc40ec738:48>
00000: ee dd cc bb 78 32 33 49 ee dd cc bb 78 32 33 49 ; ....x23I....x23I
00010: ee dd cc bb 78 32 33 49 ee dd cc bb 78 32 33 49 ; ....x23I....x23I
00020: ee dd cc bb 78 32 33 49 ee dd cc bb 78 32 33 49 ; ....x23I....x23I
</post>
</Vt>
<heappool iovs=0 allocs=6 frees=4 total=144/>
o << cy_endl << "</Vt_test_main>" << cy_endl;
o << cy_now;
</Vt_test_main>
menu
Here's a menu of pages on cy code.
license
See license and copyright for code here. For more context, see the cy page.
comments
Compared to a thorn demo, I explain cy code less: I care little whether folks use or grasp cy source. But since I aim to get ideas across, I reveal a point to code constructs so you see intentions. If you ask: What was this for? That's the only question I address: why a thing was done. If you what to know how code works or what loose ends remain, figure it out.
two space indent
Sorry about the two space indent format. If you don't like it, fix it yourself.
color coding
Library source code appears appears in amber (orange/brown): amber is_source(code* c);
Source .cpp code appears in red: void cy_logf(int, const char* f, ...) {
char temp[ 2048 + 4 ];
va_list args;
va_start(args,f);
vsnprintf(temp, 2048, f, args);
va_end(args);
temp[2048] = 0;
printf("%s\n", temp);
}
Sample test code is purple: o << "# purple=test green=stdout" << cy_newl;
Printed output on stdout is green: # purple=test green=stdout
I know these aren't the best color cues. (Amber and green might appear the same hue to color blind folks. I have excellent color discrimination myself.)
history
I first wrote cy::Vt (but named yvt in thorn) about four of five years ago, to replace std::vector in the thorn version of deck named yd. I tinkered with it a little in the last two years. I considered rewriting the guts as a btree for efficient scaling. This weekend (25apr2009) I added the use of cy_pool (see pool) to allow a C client to manage all memory.
pools
I wrote cy_pool pool api specifically to plug it into cy::Vt, with an assumption actual space might come from a fixed size memory pool (because I'll almost certainly use it that way). When memory is statically allocated in apps managing their own flow control, available memory fragments might come only in specific sizes. So cy_pool is able to return blocks of memory bigger than you requested, because it's the next size in a pool. This alters code in cy::Vt when it copes with bigger memory blocks returned. However, when no pool is provided, I left code in place calling global operators new and delete. You might not need it.
testing
This code has only been tested lightly. C-based pools for space allocation were hacked in immediately before this was written. Don't cut yourself on sharp edges. (I expect to test this more soon when a clone is used in prototype code at work.) A few test classes appear on this page: see I32x: an example 32-bit integer with aggressive tagging to see problems, and VtI32x: a vector of these with debug printing support. The C-based out stream api is cy_sink shown on the separate sink page. (Classes used only for testing show why much of cy code is a kludge: intent is to see whether code works.)
iovecs
You might see many synonyms for struct iovec, including cy_iovec, cy_iov, cy::Iov, cy::Iovec, etc. They all mean the same thing. Conversion cost is trivial. C++ iovs typically have constructors and operators converting between all of them. See run and iovec demoes for iov docs. |