Dec 252017
 
Container Lite v1.82 - a portable hybrid container class for C++, requires a fraction of the space used by commercial container class libraries.
File CL182.ZIP from The Programmer’s Corner in
Category C++ Source Code
Container Lite v1.82 – a portable hybrid container class for C++, requires a fraction of the space used by commercial container class libraries.
File Name File Size Zip Size Zip Type
CL.CPP 24573 5180 deflated
CL.DOC 50143 14073 deflated
CL.HPF 15440 2936 deflated
CL.HPP 8990 2572 deflated
CL.HPT 13742 2955 deflated

Download File CL182.ZIP Here

Contents of the CL.DOC file


Container Lite (CL version 1.82: 5/18/93)
A Portable Persistent Hybrid Container Class for C++
Copyright 1993, John Webster Small, All rights reserved
PSW / Power SoftWare, P.O. Box 10072, McLean, VA 22102
(703) 759-3838

This version of Container Lite is licensed to you as shareware. For
details, see section 5 below.


1.0 Introduction

Applications using Container Lite use only a small fraction of
the container code space consumed by other commercial and
compiler bundled container class libraries. Use Container Lite
repeatedly throughout all your applications for even greater
savings in design and coding effort as well as drastically
reducing their sizes. Container Lite is ideal for coding games,
rolling your own application frameworks, and any application
requiring user configurations to persist.

Container Lite is a portable, persistent, hybrid container class
library compatible with compilers supporting AT&T's C++ 2.x, 3.x
standard. Container Lite is based upon a single container class
having a hybrid stack, queue, deque, list, array interface and
built-in sort, search, and iterate member functions.


1.1 Implementation

Container Lite is implemented as a dynamically sized array of void
pointers, pointing to the data you are collecting. Template
invocations additionally define primitives for on the fly copy
initialization, assignment, and destruction of your data nodes
automatically. Container Lite's "form template" provides the same
automatic code generation and strong type checking for compilers not
yet supporting templates.


1.2 Installation

Copy the following Container Lite files to the appropriate development
directory for your C++ compiler if you have not already done so.

cl.docthis file
cl.hppContainer Lite header
cl.cppContainer Lite source

cl.hptContainer Lite template
cl.hpfContainer Lite form template


2.0 Hybrid Container

Always think of Container Lite in terms of its multi-faceted hybrid
behavior. One moment your container is a stack, the next a
priority queue or array, etcetera. With Container Lite you are
never locked into some arcane branch class of a convoluted,
towering hierarchy like you would be with a conventional "textbook"
container implementation.


2.1 Elastic Array

Container Lite's elastic array behavior provides the foundation for
the rest of its inherent behaviors. Imagine an array in which you
can insert or extract cells at any location. Container Lite lets
you specify the granularity and hysteresis of this elasticity in
constructor and/or housekeeping calls with a delta parameter. In
other words, when a new cell needs to be added how many additional
spare cells should be provided to optimize future expansion
efforts? And how many spare cells should be allowed to accumulate
before compaction? The following list of member functions catalogs
this dynamic array behavior.

vector() Limit() setLimit()
pack() Delta() setDelta()
Nodes() MaxNodes() setMaxNodes()
vacancy() vacancyNonElastic()
atIns() atInsNew() atRmv()
allRmv() atDel() atDelAsg()
allDel() atPut() atPutNew()
atPutAsg() atGet() operator[]()
atGetAsg() atXchg() index()
forEach()

If you use Container Lite's template or "form template" to generate
a strongly typed checked container wrapper, you can then assign and
clone your data on the fly. Primitives with "Asg" and "New"
suffixes assign and clone your data respectively. Templates can
also provide persistence if your data has overloaded stream
insertion and extraction operators along with a default
constructor. If not you still have the option of defining template
mediator functions or accepting binary defaults.

Elastic array operation is really summed up by two primitives,
atIns() and atRmv(). AtIns() writes a pointer value to a newly
inserted cell in the container's logical array at the index
indicated. The cell originally at the index and all successive
cells are pushed back one index location, expanding the physical
array if necessary. AtRmv() performs the inverse function of
atIns(), extracting the indexed cell returning its pointer while
pulling all successive cell indices forward by one. The physical
array is shrunk automatically if excessive space has accumulated.

If you use the Container Lite template to generate a strongly typed
check wrapper class for your data, you can then use the above
primitives with "Asg", "New", and "Del" suffixes. These primitives
allow you to assign, clone, and delete your data nodes on the fly.
Persistence is also provided which we will see in our first
example.

// examp201.cpp -- link with cl.obj

#include // rand(), srand()
#include // time_t, time()
#include "cl.hpt"

#define intFile "ints.tmp"

main()
{
time_t t; srand((unsigned) time(&t));
CL ci(CL_ANDS,15);
int i = 0;
while (ci.atInsNew(rand()%(ci.Nodes()+1),&i))
i++;
ci.save(intFile);
ci.allClr();
ci.load(intFile);
cout << "0-14 in random order: " << endl;
while (ci.atDelAsg(0,&i))
cout << i << endl; return 0;
}

CL generates a wrapper class for integers. The CL_ANDS
composite flag enables the container's "(A)sg", "(N)ew", "(D)el"
and stream (S)ave primitives. Actually CL_ANDS is defined as the
flags expression: CL_ASG | CL_NEW | CL_DEL | CL_SAVE. This
container is also limited to a maximum of 15 items.

Next, the numbers zero through fourteen, i.e. the values of the
variable i, are inserted at random locations in the "array."
Notice that the first parameter of the atInsNew() primitive is
the "at" parameter. Cells of the elastic array are indexed

starting at zero just like a conventional C++ array. Thus the
last cell index of the elastic array is Nodes() - 1. The "at"
location selected for insertion is a random number between zero
and Nodes(). Any attempt to insert at a location greater than
Nodes()-1 is queued onto the end of the array.

The second parameter of the atInsNew() primitive is the "insert
what" parameter. The "New" suffix indicates that the item to be
inserted must be cloned on the fly and that this clone is to be
bound in the container instead of the original variable i in this
case. To bind means to store the address of a item in a cell of
the container's elastic array. All item parameters of all
Container Lite primitives are pointers. Thus we take the address
of the variable i.

Most Container Lite primitives return integers or pointers. Such
primitives indicate failure by returning zero. In our example
atInsNew() returns a pointer to the newly cloned integer being
bound. When maxNodes is reached, i.e. 15, atInsNew() returns
NULL indicating failure and the while loop terminates.

Since the CL_SAVE flag is set (via CL_ANDS), the save() primitive
is enabled and our container of integers is saved in the file
"ints.tmp." The complete state of the container is saved in the
file, e.g. flags, maximum node setting, etcetera. If you already
have an open stream handle you could use the stream insertion
operator instead of save().

Since the CL_DEL flag is set (via CL_ANDS), the allClr()
primitive calls allDel() instead of allRmv(). AllRmv() releases
all binding addresses, removing their cells from the elastic
array. AllDel does the same thing except that the nodes are
deleted upon removing with the delete operator via a call to the
container's deleteD() protected scope virtual function.

There is no CL_LOAD flag since a container on a stream would
never have the option of having a reset load flag. The load()
primitive calls allClr() before attempting to load the container
from stream. Since the loaded items are naturally dynamic the
CL_DEL flag is set if it isn't already. The complete state of
the container saved in stream is restored.

The first parameter of atDelAsg() is the "at" parameter and the
second is the "assign to what" parameter. After assignment is
made the original container node is deleted via a call to the
container's deleteD() protected scope virtual function.
AtDelAsg() returns the address of the variable i each time until
the container is empty at which time it returns NULL. Both the
CL_DEL and CL_ASG flags must be set for atDelAsg() to be enabled.

When leaving the scope of main() the container's destructor is
automatically called. If CL_DEL flag is set, allDel() is called
instead of allRmv(). This is irrelevant in our example since all
nodes have already been disposed of.

The logical basis for primitive naming is as follows. The
atInsNew() primitive performs the same operation as atIns()
except the data is cloned on the fly and the clone is bound in
the container instead of the variable i. Again, to bind means to
store the address of a node in a cell of the container's elastic
array. In either case a pointer to the bound node is returned to
indicate success or NULL otherwise. AtDelAsg() basically
performs an atGetAsg(), (As)si(g)ning (copying) the outgoing node
to the variable i, before performing an equivalent atDel() which
is the same thing as an atRmv() except the outgoing node is
(Del)eted and not just the cell (R)e(m)o(v)ed from the array.

If your compiler doesn't support templates you can use the "form
template" approach shown below instead.

// examp202.cpp -- link with cl.obj

#include // rand(), srand()
#include // time_t, time()

#define ITEM int
#define CL CL_int
#include "cl.hpf"

#define intFile "ints.tmp"

main()
{
time_t t;
srand((unsigned) time(&t));
CL_int ci(CL_ANDS,15);
int i = 0;
while (ci.atInsNew(rand()%(ci.Nodes()+1),&i))
i++;
ci.save(intFile);
ci.allClr();
ci.load(intFile);
cout << "0-14 in random order: " << endl;
while (ci.atDelAsg(0,&i))
cout << i << endl;
return 0;
}

Notice that you must define ITEM as the parameter type that would
normally be passed as the template parameter, i.e. "#define ITEM
int" is the equivalent of passing "int" to "CL<>." Likewise CL is
defined as a new type name so that you use "CL_int" in place of
"CL."

The next example shows a container being used to sort a standard
C++ vector of strings. Both the template and form template
approaches are shown.

// examp203.cpp - link with cl.obj

// #define CPP_TEMPLATES

char *V[] = {
"Vectors of pointers can",
"be exploded into containers",
"for processing. A container",
"can also be imploded into",
"a vector of pointers to",
"its nodes.",
0
};

#if defined(CPP_TEMPLATES)
#include "cl.hpt"
ITEM_str
#else
#define ITEM_str
#include "cl.hpf"
#endif

#define vectorFile "vector.tmp"

main()
{
CL_str v(V,0,CL_SAVE); // explode constructor
cout << "\n\nExploded, unsorted vector ...\n\n";
for (unsigned i = 0; v[i]; i++)
cout << (char *) v[i] << "\n";
v.save(vectorFile);
v.allClr();
v.load(vectorFile);
v.sort (); // sort
v.vector(V); // implode
cout << "\n\nImploded, sorted vector ... \n\n";
for (i = 0; v[i]; i++)
cout << v[i] << "\n";
return 0;
}

The vector() function will allocation a dynamic vector if one
isn't provided as a parameter. The overloaded subscript
operator, i.e. [], is a convenient notation for calling atGet().
For details about any Container Lite member function or data be
sure to refer to the reference chapter (available to registered
users). Remember, the examples given in this chapter are meant
to give you a feel for Container Lite's different modes of
operation, i.e. elastic-array, stack-queue-deque, list, and
sort-search-unique. Thus the descriptions here don't focus on
member function details.

You may have noticed by now that neither iostream.h nor iomanip.h
is ever included in our examples. That's because Container Lite
header files already pull them in. And string.h is automatically
pulled in by the (form) template header.


2.2 Stack-Queue-Deque

A stack provides LIFO (Last In, First Out) storage. Container Lite
has a complete set of stack primitives. These functions don't
disturb the list behavior of a container, e.g. current node
setting. A queue provides FIFO (First In, First Out) storage. And
the deque (pronounced deck) provides FOLO (First Out, Last Out)
storage. Think of a deque as a combination stack-queue with the
additional capability of being able to be popped from the rear.
The following is a list of the member functions supporting this
stack-queue-deque behavior:

push() pushNew() pop()
popDel() popDelAsg() top()
topAsg() insQ() insQNew()
unQ() unQDel() unQDelAsg()
rear() rearAsg() operator<<()
operator>>()

The example for this section uses a container of floats.

// examp204.cpp -- link with cl.obj

// #define CPP_TEMPLATES

#if defined(CPP_TEMPLATES)
#include "cl.hpt"
#define CL_float CL
#else
#define ITEM float
#define CL CL_float
#include "cl.hpf"
#endif

#define floatFile "floats.tmp"

main()
{
CL_float cf(CL_ANDS,10);
for (float i = 1.0; cf.pushNew(&i); i++);
cf.save(floatFile);
cf.allClr();
cf.load(floatFile);
cout << "Count to 10: " << endl;
while (cf.unQDelAsg(&i))
cout << i << endl;
return 0;
}

CL_ANDS (equivalent to the CL_ASG, CL_NEW, CL_DEL, and
CL_SAVE flags) is set in the constructor call this time. Without
CL_NEW being set, pushNew() would have been inhibited and all
other member functions with the "New" suffix. Without CL_ASG
and CL_DEL being set, unQDelAsg() would also have been
inhibited along with all other functions with "Asg" and/or "Del" in
the names. Without CL_SAVE the save() primitive would also have
been inhibited. The various flags are explained in detail in the
reference chapter.

This container is limited to a maximum of 10 items. That's because
10 was passed to the constructor parameter named "maxNodes." This
can be changed at any time with a call to the setMaxNodes()
function that you saw cataloged in the previous section. The unQ()
family of member functions work on the rear of the queue thus
providing deque behavior.

The container's destructor is automatically called just before
"return 0;." Since the previous while loop has already deleted all
nodes there is nothing left to delete. However, you should take
note that if a container's CL_DEL flag is raised the destructor
will attempt to delete any remaining nodes, otherwise the nodes are
simply removed. Thus you should typically segregate you containers
into two groups: those with static nodes and those with dynamic.
You don't want to accidently delete a statically allocated node
that is mingling among the dynamically allocated ones! If you mix
the two types it's safest not to set the CL_DEL flag since a
compiler generated destructor call could then jump out and bite
you!


2.3 List

Any container can be treated as if it were a doubly linked list
thereby providing sequential storage. In a container's header
structure a current node index is maintained that lets the list
primitives know where the insertion or removal is to take place.
The dynamic-array and stack-queue-list primitives don't disturb
this current node index unless of course it points to the cell
that's being removed. In that case the current node becomes
undefined just like it was when the container was first
constructed. When a container is saved in a file the current
node setting is saved too. When the container is reloaded its
previous current node setting is restored. Here's a list of the
list primitives.

CurNode() setCurNode() ins()
insNew() rmv() del()
delAsg() put() putNew()
putAsg() get() getAsg()
next() operator++() nextAsg()
prev() operator--() prevAsg()

Let's look at a little more rigorous, realistic example this time.
We'll create a container of Employees. You'll learn more in the
next chapter about using (form) templates for various data types.

// examp205.cpp -- link with cl.obj

// #define CPP_TEMPLATES

#include
#include "cl.hpp"

class Employee {
char *name;
unsigned salary;
int cmp(const Employee& e) const;
static Employee * THIS;
static ostream& SHOW(ostream& os)
{
return os << "Employee name: "
<< setw(20)
<< (THIS->name? THIS->name : "n/a")
<< "\tsalary: " << THIS->salary;
}
public:
Employee(const char * name = 0,
unsigned salary = 0)
{
this->name = (name? strdup(name) : 0);
this->salary = salary;
}
Employee(const Employee& e)
{
name = (e.name? strdup(e.name) : 0);
salary = e.salary;
}
Employee& operator=(const Employee& e)
{
delete name;
name = (e.name? strdup(e.name) : 0);
salary = e.salary;
return *this;
}
int operator==(const Employee& e) const
{ return !cmp(e); }
int operator>(const Employee& e) const
{ return (cmp(e) > 0); }
~Employee() { delete name; }
ostream& (*show())(ostream&)
{ THIS = this; return SHOW; }

friend ostream& operator<<
(ostream& os, Employee& e)
{ return os << &e.name << endm << e.salary; }
friend istream& operator>>
(istream& is, Employee& e)
{ return is >> &e.name >> nextm >> e.salary; }
};

Employee * Employee::THIS;

int Employee::cmp(const Employee& e) const
{
if (!name)
if (!e.name)
return 0;
else
return -1;
else
if (!e.name)
return 1;
else
return strcmp(name,e.name);
}


#if defined(CPP_TEMPLATES)
#include "cl.hpt"
#define CL_Employee CL
#else
#define ITEM Employee
#define CL CL_Employee
#include "cl.hpf"
#endif

#define EmployeeFile "employs.tmp"

main()
{
CL_Employee cE(CL_ANDS);
cE.ins(new Employee("Doe, John",1000));
Employee E("Small, John",100);
cE.insQNew(&E);
cE.push(new Employee("Morgan, Maria",10000));
cE.save(EmployeeFile);
cE.allClr();
cE.load(EmployeeFile);
cE.sort();
cout << "\nEmployees in alphabetical order: "
<< endl;
while (cE.nextAsg(&E))
cout << E.show() << endl;
return 0;
}

If you compiler supports templates you have the option of
uncommenting the second line "#define CPP_TEMPLATES."

Thus far we have only seen strongly typed checked containers. We
could have just as easily used a typeless container which the next
example demonstrates. However we loose the use of "Asg" and "New"
primitives along with persistence. After all, how can a container
copy, clone, or store data that it knows nothing about? Likewise
how can it sort and search untyped data?

// examp206.cpp -- link with cl.obj

#include // strcmp()
#include "cl.hpp"

main()
{
cl s;
s.ins("in typeless containers!");
s.ins("Heterogenous data");
s.ins("can be easily bound");
// s.CurNode() == 2
s.insNew("This string won't appear!");
s.sort(CLcmPcast(strcmp,void));
// s.CurNode() == CL_NOTFOUND != 0
while (++s)
cout << (char *)s.get() << endl;
return 0;
}

Notice that "cl" is a typeless container where as CL was used with
templates. The reason the fourth string won't appear is that
neither the CL_NEW flag was set in the constructor call nor was
the protected scope assignD() virtual function overridden during
template generation to handle strings. Notice that the sort()
function takes an optional compare function pointer parameter. The
CLcmPcast() macro type casts the standard library's strcmp()
function pointer to one taking constant void pointer parameters.
Many times when working with non persistent heterogeneous data it
is easiest to play fast and loose with a typeless container.

When the current node is rmv()'ed or del()'ed its predecessor is
made the new current node. For example, if the 5th node is
rmv()'ed then CurNode() returns 4. However, if the current node
being 0 is rmv()'ed then the new current node remains 0 until there
are no more nodes and the current node then becomes undefined,
which is not 0! When the current node is undefined, CurNode()
returns the constant CL_NOTFOUND. The operation of rmv() is
designed to perform the inverse function of ins() which inserts
after the current node making the new node current. An ins()
followed by rmv() will leave a list in its original state.


2.4 Sort-Search-Unique Category

Containers can also be sorted and searched on a default or user
supplied compare function. You don't have to derive a new class
for each different sort order! Only template and "form template"
generated containers have default compare functions (but only if
the template parameter type has implicitly or explicitly defined
relational operators or overrides the template's compare mediator
function). If a user defined compare function is registered it
can persist also. Please note that these primitives affect the
list's current node setting.

Sorted() unSort() setCmP()
::RegisterCmP() CmP() sort()
insSort() insSortNew() insUnique()
insUniqueNew() findFirst() operator[]
findNext() findLast() findPrev()
findAll()

With these primitives you can build bags, sets, and even
dictionaries which our next example will demonstrate. One common
challenge to any programmer is to produce an internationalized
version of his/her application. The following string resource
suggests one approach. In our implementation, the first word in a
string is the token/key for the remainder of the string. To change
language versions, edit the remainder of each string. Since all
token/key - translation strings are to be located together the task
should be easy.

// examp207.cpp -- link with cl.obj

// #define CPP_TEMPLATES

#include

#if defined(CPP_TEMPLATES)
#include "cl.hpt"
ITEM_str
#else
#define ITEM_str
#include "cl.hpf"
#endif


class StrReSrc : CL_str {
static unsigned ridx;
static int cmp
(const char * S1, const char * S2);
protected:
virtual voidCmP cmPD(voidCmP cmP)
{ return (cmP? cmP :CLcmPcast(cmp,void)); }
public:
StrReSrc() : CL_str(CL_SAVE) {}
StrReSrc(const char * filename)
: CL_str(defaultConstruct)
{ (void) CL_str::load(filename); }
int load(const char * filename)
{ return CL_str::load(filename); }
int save(const char * filename)
{ return CL_str::save(filename); }
int add(char * S)
{ return (insUnique(S)?1:0); }
const char * operator[](const char * S)
{ return (findFirst(S)?&(((char *)get())[ridx])
: "not found"); }
CL_str::allClr;
~StrReSrc() { CL_str::destruct(); }
};

unsigned StrReSrc::ridx;

int StrReSrc::cmp(const char * S1, const char * S2)
{
for (ridx = 0; S1[ridx] == S2[ridx] ; ridx++)
{
if (S1[ridx] == '\0')
return 1;
if (isspace(S1[ridx]))
return 0;
}
if (S1[ridx] == '\0' || S2[ridx] == '\0')
return 0;
return 1;
}

#define StrReSrcFile "spanish.tmp"

main()
{
StrReSrc sr;
sr.add("one uno");
sr.add("two dos");
sr.add("three tres");
sr.add("three wrong");
sr.save(StrReSrcFile);
sr.allClr();
sr.load(StrReSrcFile);
cout << "\nCount to three in Spanish: "
<< endl;
cout << sr["one"] << endl;
cout << sr["two"] << endl;
cout << sr["three"] << endl;
return 0;
}

The (form) template wrapper for strings is used to derive a
string resource. The cmPD() virtual function is then overridden
to make our token/key compare function the default. Notice how
the default subscript operator, i.e. operator[](), is overloaded
to allow the token/key to access the translation/value. Hence we
see that a dictionary action is really defined by the supplied
compare function and overloaded access function. But recall a
dictionary is essentially a set. The add() function defined here
assures us that only unique elements (token/keys) are allowed to
belong to the string resource dictionary (set). To become a bag
the add() function would need to be changed to call insSort()
instead of insUnique(). And to determine the count of a
particular bag item one would call findAll().

Once a container is sorted, it will remain in a sorted state, as
far as the container is concerned, until the compare function is
changed or a new node is added without insSort???() or
insUnique???(). However, it is possible for you to access a
node, modifying the key value, without the container being aware
that the sorted order has been spoiled. Be sure to use UnSort()
to let the container know if it is no longer sorted in these
cases.

With other commercial and compiler bundled container libraries, you
would have been forced to derived a new class for each different
key.


3.0 Strong Type Checking


Container Lite's foundation class is named "cl." The "cl" class
used by itself performs no type checking on the data being bound
within. These typeless containers are useful for quick and dirty
handling of heterogeneous data types. However, by using a template
a container can be made to impose strong type checking for any type
of data at compile time, thereby restricting the use of the
container to a particular data type. Even if your compiler doesn't
support templates, strong type checking can still be achieved with
Container Lite's "form templates." You have already seen all three
approaches in the examples of the previous chapter.


3.1 A Well Endowed Data Type

A fully functional container requires its nodes' data type (ITEM)
to be well endowed having (either implicitly or explicitly
defined):

1. an overloaded equality operator, i.e.

int ITEM::operator==(const ITEM&) const;

or

int operator==(const ITEM&, const ITEM&);
//friend

and an overloaded greater than operator, i.e.

int ITEM::operator>(const ITEM&) const;

or

int operator>(const ITEM&, const ITEM&);

2. a copy initializer constructor, i.e.

ITEM::ITEM(const ITEM&);

3, an overloaded assignment operator, i.e.

ITEM& ITEM::operator=(const ITEM&);

4. an overloaded stream insertion operator, i.e.

ostream& operator<<(ostream&,ITEM&);

5. an overloaded stream extraction operator, i.e.

istream& operator>>(istream&,ITEM&);

and a default constructor, i.e.

ITEM::ITEM();

and

6. a destructor if one is required, i.e.

ITEM::~ITEM();

Let's look at a well endowed string class example.

// examp301.cpp -- link with cl.obj

// #define CPP_TEMPLATES

#include
#include "cl.hpp"


class String {
char *str;
size_t len;
int cmp(const String& cs) const;
public:
String(const char * s = (const char *)0)
{
str = (s? len = strlen(s), strdup(s)
: (char *)(len = 0));
}
String(const String& s)
{
str = (((len = s.len) != 0)?
strdup(s.str) : (char *)0);
}
~String() { delete str; }
String& operator=(const String& s)
{
delete str;
str = (((len = s.len) != 0)?
strdup(s.str) : (char *)0);
return *this;
}

inline int operator==(const String& cs) const
{ return !cmp(cs); }
inline int operator> (const String& cs) const
{ return (cmp(cs) > 0); }
operator const char *() { return str; }
friend ostream& operator<<(ostream& os, String& s)
{ return os << &s.str; }
friend istream& operator>>(istream& is, String& s)
{
is >> &s.str;
s.len = (s.str? strlen(s.str) : 0);
return is;
}
};

int String::cmp(const String& cs) const
{
if (!str)
if (!cs.str)
return 0;
else
return -1;
else
if (!cs.str)
return 1;
else
return strcmp(str,cs.str);
}


#ifdef CPP_TEMPLATES
#include "cl.hpt"
#define CL_String CL
#else
#define ITEM String
#define CL CL_String
#include "cl.hpf"
#endif

#define StrFile "strings.tmp"

main()
{
CL_String cS(CL_ANDS);
String s("can be");
cS.insNew(&s);
cS.ins(new String("tamed!"));
cS.insQ(new String("Now strings"));
cS.sort();
cS.save(StrFile);
cS.allClr();
cS.load(StrFile);
while (cS.popDelAsg(&s))
cout << (const char *)s << endl;
return 0;
}

Of course the compiler can supply a default constructor, copy
initializer constructor, assignment operator, and destructor if
need be. But overloaded relational operators, and stream operators
can't be inferred implicitly by the compiler for non native types.
The next example reworks examp205.cpp to allow these defaults to be
meaningful, however the (form) template still needs to be told to
perform binary compare and stream operations.

// examp302.cpp -- link with cl.obj

// #define CPP_TEMPLATES

#include
#include "cl.hpp"

class Employee {
char name[20];
unsigned salary;
static Employee * THIS;
static ostream& SHOW(ostream& os)
{
return os << "Employee name: "
<< setw(20)
<< (THIS->name? THIS->name : "n/a")
<< "\tsalary: " << THIS->salary;
}

public:
Employee(const char * name = 0,
unsigned salary = 0)
{
if (name)
strncpy(this->name,name,
sizeof(Employee::name));
else
this->name[0] = '\0';
this->salary = salary;
}
ostream& (*show())(ostream&)
{ THIS = this; return SHOW; }
};

Employee * Employee::THIS;

#if defined(CPP_TEMPLATES)
#include "cl.hpt"
ITEM_BINARY_CMP_STRM(Employee)
#define CL_Employee CL
#else
#define ITEM Employee
#define ITEM_BINARY_CMP_STRM
#define CL CL_Employee
#include "cl.hpf"
#endif

#define EmployeeFile "employs.tmp"

main()
{
CL_Employee cE(CL_ANDS);
cE.ins(new Employee("Doe, John",1000));
Employee E("Small, John",100);
cE.insQNew(&E);
cE.push(new Employee("Morgan, Maria",10000));
cE.save(EmployeeFile);
cE.allClr();
cE.load(EmployeeFile);
cE.sort();
cout << "\nEmployees in alphabetical order: "
<< endl;
while (cE.nextAsg(&E))
cout << E.show() << endl;
return 0;
}

Notice that Employee::name has been changed from a pointer into an
array allowing the defaults and binary operations to be meaningful.
The ITEM_BINARY_CMP_STRM macro allowed us to configure the (form)
template for Employee's deficiencies, i.e. no relation or stream
operators. Please note that binary operators must not be performed
on structures or classes having virtual functions since virtual
function table pointers are hidden within and can be improperly
overwritten.


3.2 Deficient Data Types

What do you do if your data is deficient in one or more of the six
requisites of (form) templates? One solution is to turn off
dependent features, e.g. persistence, "Asg", "New", and "Del"
primitives, in the wrapper generated by the (form) template. The
following macros are available for this purpose:

ITEM_NO_REL_OPS ITEM_NO_ASSIGN
ITEM_NO_COPYINIT ITEM_NO_DELETE
ITEM_NO_STRM_INSERT ITEM_NO_STRM_EXTRACT

For example, suppose that your data type is deficient in having no
overloaded relational or assignment operators and that default
assignment is unacceptable because of imbedded pointers. You can
use the (form) template facility to generate a strongly type
checked wrapper with the follow code.

#if defined(CPP_TEMPLATES)
#include "cl.hpt"
ITEM_NO_REL_OPS(???)
ITEM_NO_ASSIGN(???)
#define CL_??? CL
#else
#define ITEM ???
#define ITEM_NO_REL_OPS
#define ITEM_NO_ASSIGN
#define CL CL_???
#include "cl.hpf"
#endif

Of course your container would have no default compare function
associated with it, nor would the "Asg" primitives be operational.
Note: all the macros defined for use with Container Lite's "form
template" are automatically left undefined after the inclusion of
cl.hpf so that the procedure can be used repeatedly to instantiate
various container types within the same file. Additional available
macros and their effects are listed below.

ITEM_BINARY_CMP compare data as binary blocks
ITEM_BINARY_STRM stream data as a binary blocks
ITEM_BINARY_CMP_STRM compare and stream data as binary
blocks
ITEM_NO_STRM_OPS prevent persistence
ITEM_NO_REL_STRM_OPS no default compare, prevent
persistence
ITEM_DELETE_ONLY no default compare, inhibit "Asg"
primitives, inhibit "New"
primitives, prevent persistence
ITEM_BIND_ONLY no default compare, inhibit "Asg"
primitives, inhibit "New"
primitives, inhibit "Del"
primitives, prevent persistence

Add whatever macros you must to accurately describe the
deficiencies of your data type to Container Lite's (form) template
facility. Now let's see if you were paying attention. What would
the code be to build a wrapper for strings? The answer:

#if defined(CPP_TEMPLATES)
#include "cl.hpt"
ITEM_DELETE_ONLY(char)
#define CL_char CL
#else
#define ITEM char
#define CL CL_char
#include "cl.hpf"
#endif

The CL_char container will have no default compare function,
inhibited "Asg" and "New" primitives, and no persistence
capabilities. The only thing gained over the typeless container of
examp206.cpp was strong type checking. Hey, wait just a minute!
Back in examp203.cpp we used the following:

#if defined(CPP_TEMPLATES)
#include "cl.hpt"
ITEM_str
#else
#define ITEM_str
#include "cl.hpf"
#endif

and CL_str was the type name for a container of strings. If we
look in cl.hpt we will find the following code.

inline void * CL_newD(const char * D)
{ return strdup(D); }
#define ITEM_str int CL_cmpD \
(const char * D1, const char * D2) \
{ return strcmp(D1,D2); } \
ITEM_NO_ASSIGN(char)
#define CL_str CL

The equivalent can be gleaned from cl.hpf. As we can see ITEM_str
invokes the ITEM_NO_ASSIGN macro. Thus the CL_str container of
strings has inhibited "Asg" primitives. But what about these
CL_newD() and CL_cmpD() functions? These are (form) template
mediator functions which modify the code generated to incorporate
behavior not naturally found in your data type.


3.3 (Form) Template Mediator Functions

The (form) template automatically generates mediator functions to
configure a container wrapper for your data type. The macros
outlined in the previous section are expanded into overriding
mediator functions that inhibit container functionality in some
form. You can also manually override them directly to extenuate
deficiencies of your data type without loss of container
functionality. Let's see a rework for a seriously deficent
employee structure.

// examp303.cpp -- link with cl.obj

// #define CPP_TEMPLATES

#include
#include "cl.hpp"

struct Employee {
char *name;
unsigned salary;
Employee(const char * name,
unsigned salary = 0)
{
this->name = (name? strdup(name) : 0);
this->salary = salary;
}
~Employee() { delete name; }

friend ostream& operator<<
(ostream& os, Employee& e)
{
return os << "Employee name: "
<< setw(20)
<< (e.name? e.name : "n/a")
<< "\tsalary: " << e.salary;
}
};

int CL_cmpD(const Employee * E1, const Employee * E2)
{
if (!E1->name)
if (!E2->name)
return 0;
else
return -1;
else
if (!E2->name)
return 1;
else
return strcmp(E1->name,E2->name);
}

inline void * CL_assignD
(Employee * D, const Employee * S,
unsigned /* flags */)
{
delete D->name;
D->name = (S->name?
strdup(S->name) : 0);
D->salary = S->salary;
return D;
}

inline void * CL_newD(const Employee * E)
{
Employee * D = new Employee(0);
if (!D) return 0;
D->name = (E->name? strdup(E->name) : 0);
D->salary = E->salary;
return D;
}

inline void CL_deleteD(Employee * E)
{ delete E->name; E->name = 0; delete E; }

inline ostream& operator<<(ostream& os, Employee ** E)
{
return os << &((**E).name) << endm
<< (**E).salary << endm;
}

inline istream& operator>>(istream& is, Employee ** E)
{
if (!E) return is;
if (*E) delete *E;
if ((*E = new Employee(0)) != (Employee *)0)
is >> &((**E).name) >> nextm
>> (**E).salary >> nextm;
return is;
}


#if defined(CPP_TEMPLATES)
#include "cl.hpt"
#define CL_Employee CL
#else
#define ITEM Employee
#define ITEM_CMP_DEF
#define ITEM_ASSIGN_DEF
#define ITEM_COPYINIT_DEF
#define ITEM_DELETE_DEF
#define ITEM_STRM_INSERT_DEF
#define ITEM_STRM_EXTRACT_DEF
#define CL CL_Employee
#include "cl.hpf"
#endif

#define EmployeeFile "employs.tmp"

main()
{
CL_Employee cE(CL_ANDS);
cE.ins(new Employee("Doe, John",1000));
Employee E("Small, John",100);
cE.insQNew(&E);
cE.push(new Employee("Morgan, Maria",10000));
cE.save(EmployeeFile);
cE.allClr();
cE.load(EmployeeFile);
cE.sort();
cout << "\nEmployees in alphabetical order: "
<< endl;
while (cE.nextAsg(&E))
cout << E << endl;
return 0;
}

Instead of using: we defined: and:

ITEM_NO_REL_OPS CL_cmpD() ITEM_CMP_DEF
ITEM_NO_ASSIGN CL_assignD() ITEM_ASSIGN_DEF
ITEM_NO_COPYINIT CL_newD() ITEM_COPYINIT_DEF
ITEM_NO_DELETE CL_deleteD() ITEM_DELETE_DEF
ITEM_NO_STRM_INSERT operator<<() ITEM_STRM_INSERT_DEF
ITEM_NO_STRM_EXTRACT operator>>() ITEM_STRM_EXTRACT_DEF

Notice that CL_cmpD() isn't inlined since we need a compare
function pointer. The rest are inlined since they will only be
expanded one time in the overridden virtual functions of the
container.

CL_deleteD() isn't really necessary since the Employee structure
already has a destructor.

Notice that the stream insertion and extraction operators have
(ITEM **) pointer parameters. This is to insure that these don't
collide with already existing operators. For example, C++ string
pointers already have overloaded stream operators. Cl.hpp/cpp
defines the following string stream operators:

inline ostream& operator<<(ostream& os, char ** D)
{ return CL_puts(os,D); }

inline istream& operator>>(istream& is, char ** D)
{ return CL_gets(is,D); }

ostream& CL_puts(ostream& os, char ** D)
{
int len = (*D? strlen(*D) : 0);
if ((os << len << endm))
if (len)
os.write(*D,len);
return os;
}

istream& CL_gets(istream& is, char ** D)
{
if (!D) return is;
if (*D) {
delete *D;
*D = (char *)0;
}
long p = is.tellg();
int len;
if ((is >> len >> nextm))
if (len)
if ((*D = new char[len+1])
!= (char *)0) {
is.read(*D,len);
(*D)[len] = '\0';
return is;
}
is.seekg(p);
return is;
}

These operators store the length of the string ahead of the string
on the stream. Contrast the string extraction operator with that
defined in iostream.h. Our extraction operator allocates the
buffer while the standard extractor expects a sufficiently large
buffer.

Looking back at our example you now know the reason for passing the
address of the name pointer to the stream operators, i.e.
... os << &((**E).name) ...

and

... is >> &((**E).name) ...

Since strings will most likely appear often in your data types,
these special file stream string operators have been provided in
cl.hpp.


4.0 Polymorphic Nodes

Thus far we have only considered binding homogeneous data. While
there is no reason why we can't bind heterogeneous data, it
remains difficult to provide the full spectrum of Container Lite
functionality owing to the complexities of having to provide a
common assignment operator, copy initializer constructor, stream
operators, etcetera. Of course if this functionality isn't
required the use of CL_DELETE_ONLY with either a template or
"form template" suffices nicely.

In order for you to more readily construct polymorphic clusters of
heterogeneous data the pitem.hpp/cpp files, supplied in the registered
version, declares/defines two classes, Streamable and Mutual, either
of which you can use as the polymorphic root for your family cluster
of classes. Streamable forms the foundation of any persistent cluster
with run time typing, compatible assignment, cloning, and stream
processing of the various cluster members. Mutual is itself derived
from Streamable and additionally provides for multiple reference
arbitration in RAM as well as automatic reference resolution during
streaming operations. For example if an object with multiple
references is saved on a stream only one copy is saved. Upon
reloading the multiple links are automatically reconstructed.

You can use either Streamable or Mutual in conjunction with Container
Lite to form polymorphic tree and graph structures. A pitem.cbk file
is provided allowing you to cookbook your cluster derived classes. It
is fully commented with step by step instructions.


5.0 Shareware Registration and Distribution

This version of Container Lite is licensed to you as shareware. You
may try out Container Lite for a reasonable length of time (60 days)
to determine its fitness for your purposes. If you decide to go on
using Container Lite you must obtain a user's license by registering!
You may also redistribute (share) the Container Lite shareware package
as outlined below. Please contact PSW for OEM licensing information.


5.1 Registration

Registered users receive a full length (detailed reference) hard copy
manual, polymorphic node files (pitem.hpp, pitem.cpp, pitem.cbk), and
Container Lite source code broken down into its individual member
functions and grouped together in appropriate files to facilitate the
building of libraries (a makefile is also included). Registered users
are granted a users license that allows for the use of Container Lite
in the development of software without royalty.

-----------------------------------------------------------

Container Lite v 1.82 Registration Form


Please specify3.5" ____ or 5.25" ____
DOS formatted diskette DD ____ or HD ____


Name _____________________________________

Company __________________________________

Address __________________________________

City _______________________

State/Province _____________

Zip/Postal Code ____________

Country ____________________


Container Lite regular price:$ 49.95
Summer introductory pricing if
ordered before Sept 21, 1993:$ 20________


Shipping/Handling for U.S.$ 4
Foreign orders$ 15________

Enclose U.S. bank draft (check)
or U.S. money order payable to
PSW / Power SoftWare for:total________

And mail to:

PSW / Power SoftWare
P.O. Box 10072
McLean, Virginia 22102 8072
U.S.A.
703 759-3838

Sorry, we are not set up to accept credit card orders.

-----------------------------------------------------------


5.2 Distribution

The Container Lite shareware package consists of the following files:

cl.doccl.hppcl.cpp
cl.hptcl.hpf

Electronic Bulletin Board systems, including online commercial
services, may distribute the Container Lite shareware package provided
that the package remains intact and unaltered.

Commerical shareware distributors and computer user groups may also
distribute the Container Lite shareware package provided that the
package remains intact and unaltered in its own compressed file,
subdirectory, or diskette. Any associated literature (sales,
promotional, or distribution) must also clearly explain the shareware
concept in a conspicuous manner.


6.0 Miscellaneous

I hope you like the Container Lite. If you have any questions,
comments, or suggestions, please don't hesitate to call me. I always
look forward to hearing from you.

Happy programming!
John Small
(voice) 703 759-3838


 December 25, 2017  Add comments

Leave a Reply