home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
World of Shareware - Software Farm 2
/
wosw_2.zip
/
wosw_2
/
CPROG
/
CL182.ZIP
/
CL.DOC
< prev
next >
Wrap
Text File
|
1993-05-15
|
46KB
|
1,306 lines
Container Lite (CL version 1.82: 5/14/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.doc this file
cl.hpp Container Lite header
cl.cpp Container Lite source
cl.hpt Container Lite template
cl.hpf Container 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 <stdlib.h> // rand(), srand()
#include <time.h> // time_t, time()
#include "cl.hpt"
#define intFile "ints.tmp"
main()
{
time_t t;
srand((unsigned) time(&t));
CL<int> ci(CL_DANDS,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<int> generates a wrapper class for integers. The CL_DANDS
composite flag enables the container's "(A)sg", "(N)ew", "(D)el"
and stream (S)torage primitives. Actually CL_DANDS is defined as
the flags expression: CL_DASSIGN | CL_DNEW | CL_DDELETE |
CL_DSTORE. The numbers zero through fourteen are inserted at random
locations in the "array." AtInsNew() 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. 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 <stdlib.h> // rand(), srand()
#include <time.h> // 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_DANDS,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<int>."
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_DSTORE); // 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. 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<float>
#else
#define ITEM float
#define CL CL_float
#include "cl.hpf"
#endif
#define floatFile "floats.tmp"
main()
{
CL_float cf(CL_DANDS,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_DANDS (equivalent to the CL_DASSIGN, CL_DNEW, CL_DDELETE, and
CL_DSTORE flags) is set in the constructor call this time. Without
CL_DNEW being set, pushNew() would have been inhibited and all
other member functions with the "New" suffix. Without CL_DASSIGN
and CL_DDELETE being set, unQDelAsg() would also have been
inhibited along with all other functions with "Asg" and/or "Del" in
the names. Without CL_DSTORE 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_DDELETE 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_DDELETE 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. 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 <string.h>
#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<Employee>
#else
#define ITEM Employee
#define CL CL_Employee
#include "cl.hpf"
#endif
#define EmployeeFile "employs.tmp"
main()
{
CL_Employee cE(CL_DANDS);
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 <string.h> // 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_DNEW flag was set in the constructor call nor was
the protected scope Dassign() 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). Please note that these primitives affect the list's
current node setting.
Sorted() unSort() setCmP()
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 <ctype.h>
#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 DcmP(voidCmP cmP)
{ return (cmP? cmP :CLcmPcast(cmp,void)); }
public:
StrReSrc() : CL_str(CL_DSTORE) {}
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 DcmP() 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. 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 calls
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 <string.h>
#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<String>
#else
#define ITEM String
#define CL CL_String
#include "cl.hpf"
#endif
#define StrFile "strings.tmp"
main()
{
CL_String cS(CL_DANDS);
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 <string.h>
#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<Employee>
#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_DANDS);
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.
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<char>
#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_Dnew(const char * D)
{ return strdup(D); }
#define ITEM_str int CL_Dcmp \
(const char * D1, const char * D2) \
{ return strcmp(D1,D2); } \
ITEM_NO_ASSIGN(char)
#define CL_str CL<char>
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_Dnew() and CL_Dcmp() 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 <string.h>
#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_Dcmp(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_Dassign
(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_Dnew(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_Ddelete(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) delete *E;
if ((*E = new Employee(0)) == 0) {
char *skipName = 0;
unsigned skipSalary;
is >> &skipName >> nextm
>> skipSalary;
delete skipName;
return is;
}
is >> &((**E).name) >> nextm
>> (**E).salary >> nextm;
return is;
}
#if defined(CPP_TEMPLATES)
#include "cl.hpt"
#define CL_Employee CL<Employee>
#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_DANDS);
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_Dcmp() ITEM_CMP_DEF
ITEM_NO_ASSIGN CL_Dassign() ITEM_ASSIGN_DEF
ITEM_NO_COPYINIT CL_Dnew() ITEM_COPYINIT_DEF
ITEM_NO_DELETE CL_Ddelete() ITEM_DELETE_DEF
ITEM_NO_STRM_INSERT operator<<() ITEM_STRM_INSERT_DEF
ITEM_NO_STRM_EXTRACT operator>>() ITEM_STRM_EXTRACT_DEF
Notice that CL_Dcmp() 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_Ddelete() 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.hpt/hpf
define the following string stream operators:
inline ostream& operator<<(ostream& os, char ** D)
{
int len = (*D? strlen(*D) : 0);
if ((os << len << endm))
if (len)
os.write(*D,len);
return os;
}
inline istream& operator>>(istream& is, char ** D)
{
int len;
if (*D) {
delete *D;
*D = (char *)0;
}
if ((is >> len >> nextm))
if (len)
if ((*D = new char[len+1])
!= (char *)0) {
is.read(*D,len);
(*D)[len] = '\0';
}
else
is.ignore(len);
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.hpt/hpf.
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 specify 3.5" ____ or 5.25" ____
DOS formatted diskette
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 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.doc cl.hpp cl.cpp
cl.hpt cl.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