string/ 0040700 0000764 0000764 00000000000 07733704541 011026 5 ustar iank iank string/DblVecTest.C 0100600 0000764 0000764 00000032256 07733704351 013137 0 ustar iank iank /** \file Regression tests for the DoubleVec reference counted class. The DoubleVec class is an instantiation of the RCArray template, which supports reference counted arrays. As a result, this code serves as a regression test for RCArray. Note that the existence of these regression tests does not change that statement this code is provided without waranty. You use this code at your own risk. Areas to test:
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include "RCArray.h"
typedef RCArray
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
/**
GrowableArray template is used to create array objects that will
grow as elements are added to the end of the array. This is
similar to the STL <vector> class.
This array class is designed for dense arrays where the all
elements of the array are used.
Usage:
Elements are added to the end of the array via the append function.
Elements in the array can be accessed via the [] operator.
If the elements in the array are dynamically allocated, the user is responsible for deallocating these elements. For example, if the GrowableArray template is used to define an array of pointers to an object, the user is responsible for deallocating the objects referenced by the pointers. The GrowableArray destructor will only deallocate the array itself.
This source base implements a String container. Closely associated with the String class is a SubString class which defines sub-sections of Strings.
The String class is a reference counted object. Rather than copying data, reference counted classes increment a reference count when one object is assigned to another. For example, when a is assigned to b below, the reference count will be incremented. Both objects will share a common data object, which in this case contains the string "slithey toves".
String a = "slithey toves";
String b = a;
Reference counted objects implement copy-on-write semantics. A unique copy will be made of an object before it is modified it it shares data with other objects. For example, when a is changed by the statement below, b will remain unchanged (e.g., it will still contain "slithey toves").
a(8, 5) = "Boyd"; // a is now "slighey Boyd"
The String object is derived from an instance of the RCArray
template (e.g., RCArray
The String object is complicated because it provides a number of
functions that are specific to the String container. The DoubleVec
class is simpler. DoubleVec demonstrates how a growable
reference counted array can be derived from RCArray
Regression tests are included for the String class (StringTest.C), the SubString class (SubStrTest.C) and the DoubleVec class (DblVecTest.C). The DblVecTest.C regression tests serve as a test for the RCArray template.
The source code documentation for this software is formatted for doxygen, which is available from www.doxygen.org. Doxygen creates diagrams using the dot program, which is part of AT&T's Graphviz. Both Doxygen and Graphviz run on the major platforms (e.g., Linux, Windoz and Solaris).
Assuming that doxygen is installed on your system, along with dot you can regenerate this documentation with the command
doxygen doxygenTemplate
Copyright and Use
You may use this source code without limitation and without
fee as long as you include:
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003.
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
string/RCArray.h 0100600 0000764 0000764 00000020422 07733701275 012502 0 ustar iank iank
#ifndef RCARRAY_H
#define RCARRAY_H
/** \file
Copyright and Use
You may use this source code without limitation and without
fee as long as you include:
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include "GrowableArray.h"
#include "RCObject.h"
#include "RCPtr.h"
#include "RCBase.h"
/**
A reference counted, growable array template.
Objects that are instantiated with this template have three
characteristics:
An assignment of the object increments the reference count rather than copying memory. For example, in the code below myObj1 is initialized and then assigned to myObj2. myObj1 and myObj2 now share the same memory (there was no copy) and the reference count will be 2.
#include <iostream>
#include "RCArray.h"
typedef RCArray IntVec;
main()
{
IntVec myObj1, myObj2;
for (int i = 1; i %lt;= 10; i++) {
myObj1.append( i );
}
myObj2 = myObj1;
for (i = 0; i %lt; 10; i++) {
std::cout %lt;%lt; myObj2[i] %lt;%lt; " ";
}
std::cout %lt;%lt; std::endl;
}
If this code is complex and executed it will print:
1 2 3 4 5 6 7 8 9 10
A change to an object with multiple references results in a copy-on-write. That instance of the object will be changed while the others remain unchanged. Since a unique copy has been made, the reference count will be reduced by one. For example, the assignment
myObj1[4] = 42;
will only change myObj1. The contents of myObj2 remain {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}.
When the reference count reaches zero, the memory for the object will be automatically deallocated. This allows reference counted array used as if they were elemental values, without worrying about memory allocation and deallocation.
Only the argumentless constructor of RCArray is made public. Classes can expose the other constructors and protected functions like length() and getRefCnt() (which is useful in testing and verification) by deriving a class from an instance of this template. For example:
#include "RCArray.h"
typedef RCArray DoubleArray;
class DoubleVec : public DoubleArray
{
private:
void init(double intialVal );
public:
DoubleVec() : DoubleArray() {}
DoubleVec(size_t initialSize) : DoubleArray( initialSize ) {}
DoubleVec(size_t initialSize, double initVal ) :
DoubleArray(initialSize, initVal) {}
size_t length() { return DoubleArray::length(); }
size_t getRefCnt() { return DoubleArray::getRefCnt(); }
};
The RCArray template has a single data element, value, which is a
"smart" pointer. Note that there is no overloaded assignment
operator. The default RCArray assignment copies the value
element. This in turn results in a copy of the RCPtr instantiated
template object (the smart pointer).
The RCArray template uses the default destructor (oh, the horror!)
The default destructor will call the destructor on value which
will, in turn invoke the removeReference() function in RCObject.
This will decrement the reference count. When the reference count
reaches zero, the shared data will be deleted.
Compiling the code
The GrowableArray template will throw a runtime exception if
the array is indexed beyond the bounds of the data. At least
in the case of the Microsoft compilers, this requires that
you include the -GX flag. For example:
cl -TP -GX -Zi test.C -o test
(the -Zi flag compiles for debug).
*/
template
MyObj b = a; // b is a reference to a
MyType v = a[i]; // A right-hand-side reference
a[j] = v; // A left-hand-size reference
References counted objects share a reference to common data
until an object is modified. The copy-on-write semantics
means that a unique copy will be made before shared object
is modified. Ideally this saves memory and improves
performance.
In the example above a and b share a reference to the
same data set. When a is read, there should be no effect
(other than the read). However, when a is modified (by
being written), a unique copy is made first. Although previously
a and b referenced the same data, the write operation
will force the creation of a unique copy for a and only
a will be modified. The b object will retain its
previous value.
In a better world there would be some way that the programmer could
indicate to the C++ compiler that a particular operator []
function implements the right-hand-side or the left-hand-size
semantics.
The naive implementation (which I used in the first two versions of
a String class) turns out to be wrong. Here is the slightly
simplified code from this somewhat incorrect String class. My
intention was that the operator [] function below labeled
"LHS" would implement copy-on-write. The operator [] labeled
RHS would do a read and nothing more.
This code does not properly implement copy-on-write
//
// operator [] (RHS) with an integer index
//
inline char String::operator [](const int ix) const
{
return pShare->str[ix];
}
//
// operator[] (LHS), integer index
//
inline char & String::operator [](const int ix)
{
makeUnique(); // make a unique copy of the shared data
return pShare->str[ix];
} // operator []
As it turned out, the function labeled LHS is called in most
cases, whether the operator [] is on the left or right
hand side of the expression. This results in copy-on-read
which is, obviously, not desirable and destroys much of the utility
for a reference counted object.
The proper implementation of the String object uses an instance
of the ValRef object (instantiated for char).
inline
String::ValRef String::operator[](const int ix)
{
return ValRef( *this, ix );
}
The ValRef object is losely modeled after the Cref class in The C++ Programming Language, Third Edition by Bjarne Stroustrup, Section 11.12.
\author Ian Kaplan www.bearcave.com */ template
class SubString
{
private:
class SharedData : public RCObject
{
private:
String *pStr_;
size_t start_;
size_t subStrLen_;
public:
SharedData();
}; // class sharedData
RCPtr<SharedData> value;
}
The RCPtr "smart pointer" is designed to be instantiated with
a class that is derived from RCObject. RCObject support
the reference count. The RCPtr "smart pointer" handles assignment
by deleting the shared data of "this" and assigning it the shared
data from the right hand side object. The RCPtr template also
decrements the reference count when the destructor is called.
\author Ian Kaplan www.bearcave.com
*/
template
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include
String a("wierd operator");
const char *pStr;
pStr = a;
The compiler fills in the cast (e.g., const char *), it
does not have to be explicit. That is we don't have to
write
pStr = (const char *)a; -- unnecessary cast
A C++ compiler should issue an error if an attempt is made
to assign a String object to a "char *". The data pointed
to by the address returned by this operator should never
be changed since it belongs to the String object.
*/
String::operator const char *() const
{
const char *pCstr = "";
size_t len = value->data.length();
if (len > 0) {
if (value->data[len-1] != '\0') {
// then add a NULL terminator
value->data.append('\0');
}
pCstr = value->data.getData();
}
return pCstr;
} // operator const char *
/**
Make "this" into an empty String. If "this" is shared,
allocate a new SharedData object. If "this" is not
shared, set the length to zero.
*/
void String::newEmptyString()
{
if (value->isShared()) {
value = new SharedData();
}
else {
value->data.set_size(0);
}
} // newEmptyString
/**
Assign a character to a String
*/
String &String::operator =(const char ch)
{
newEmptyString();
if (ch != '\0') {
value->data.append( ch );
value->data.append('\0');
}
return *this;
} // operator= (String = char)
/**
Assign a C-string to a String.
The String(C-string) constructor could be used for this.
However, using this default generates code which is more
inefficient. The write() function is used here since it
properly handles the String shared data.
If a null string is assigned to a String, the result will
be the same as if an empty String were assigned. For
example:
char *pCstr = 0;
String a = "fubar";
a = pCstr; // same as if "" were assigned
*/
String &String::operator =(const char *Cstr)
{
newEmptyString();
if (Cstr != 0) {
char ch;
for (ch = *Cstr; ch != '\0'; ch = *Cstr) {
value->data.append(ch);
Cstr++;
}
value->data.append(ch);
}
return *this;
} // operator= (String = C-string)
/**
Concatenate a C string on to the end of the String object. For
example:
String s("abcd ");
s += "efgh";
will result in "s" containing "abcd efgh".
*/
String &String::operator +=( const char *Cstr )
{
if (value->isShared()) {
newSharedData();
}
// If there is a null character, delete it so we don't
// append after the null.
size_t len = value->data.length();
if (len > 0 && value->data[len-1] == '\0')
value->data.remove();
appendCString( Cstr );
return *this;
} // operator +=
/**
Concatenate two String objects.
*/
String &String::operator +=(String &s )
{
if (s.value->data.length() > 0) {
if (value->isShared()) {
newSharedData();
}
// If there is a null character, delete it so we don't
// append after the null.
size_t len = value->data.length();
if (len > 0 && value->data[len-1] == '\0') {
value->data.remove();
}
appendString( s );
}
return *this;
} // operator +=
/*
* ======== Global binary operators ========
*
*/
/**
* Global operator +: "abcd" + String(" efgh")
*
This operator supports an operation like "abcd" + String(" efgh").
The result of this operator would be a String object containing
"abcd efgh".
*/
String operator +(const char *Cstr, String &s)
{
String tmp;
if (Cstr != 0) {
tmp = Cstr;
}
tmp += s;
return tmp;
} // global operator +
/**
Global operator +: Concatenate a character and a String object.
String Str("ab");
String a;
a = 'c' + Str
Here, "a" will contain "abc"
*/
String operator +(const char ch, String &s)
{
String tmp;
tmp = ch;
tmp += s;
return tmp;
} // global operator +
/**
Global Cstr == String operator, where Cstr is a
"C" string.
*/
bool operator ==(const char *Cstr, String &s)
{
return (s == Cstr);
} // global operator ==
/**
Global Cstr != String operator, where Cstr is a
"C" string.
*/
bool operator !=(const char *Cstr, String &s)
{
return (s != Cstr);
} // operator !=
/**
* Cstr <= s: however, we must use the
* comparision s to Cstr.
* So this expression is true
* if s >= Cstr.
*/
bool operator <=(const char *Cstr, String &s)
{
return (s >= Cstr);
} // global operator <=
/**
* Cstr >= s: however, we must use the
* comparision s to Cstr.
* So this expression is true
* if s <= Cstr.
*/
bool operator >=(const char *Cstr, String &s)
{
return (s <= Cstr);
} // global operator >=
/**
* Cstr < s: however, we must use the
* comparision s to Cstr.
* So this expression is true
* if s > Cstr.
*/
bool operator <(const char *Cstr, String &s)
{
return (s > Cstr);
} // global operator <
/**
* Cstr > s: however, we must use the
* comparision s to Cstr.
* So this expression is true
* if s < Cstr.
*/
bool operator >(const char *Cstr, String &s)
{
return (s < Cstr);
} // global operator >
string/String.h 0100600 0000764 0000764 00000026163 07733672723 012462 0 ustar iank iank
#ifndef MY_STRING_H
#define MY_STRING_H
// don't want a collision with another string macro, so I have
// not used STRING_H.
/** \file
Copyright and Use
You may use this source code without limitation and without
fee as long as you include:
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include
Writing string classes has great educational value...
Chapter 20, The C++ Programming Language, third edition
Bjarne Staroustrup
String a("abcdefghijkl");
a(4, 5) = "12345";
The String object "a" now contains "abcd12345jkl"
This function returns a SubString object. The SubString object
supports the assignment operator, which will assign the
C-string to a
*/
inline
SubString String::operator ()(const size_t start, const size_t len)
{
SubString subStr( *this, start, len );
return subStr;
}
/**
Append a String and a SubString
String a = "abcde"; // indices: 01234567 String b = "123 5678"; String c = a + b(3,4); // result will be "abcde 567"*/ inline String String::operator +(const SubString &sub) { String section = sub; String result = *this + section; return result; } // operator + /** Assign a SubString to a String. For example:
String a = "lazy dog";
String b = a(5, 3);
*/
inline
String &String::operator =(const SubString &sub)
{
*this = (String)sub;
return *this;
} // operator =
/**
Allocate a new SharedData object which contains a copy of the old
data. Note that this code relies on the fact that value is
a "smart pointer" which will decrement the reference count and
deallocate the space if the reference count is zero.
*/
inline
void String::newSharedData()
{
value = new SharedData( value->data );
}
/**
Compare the character string in "this" object with the
argument Cstr.
if (this < Cstr) return -1;
if (this == Cstr) return 0;
if (this > Cstr) return 1;
*/
inline
int String::compareTo( const char *Cstr )
{
const char *pThisCStr = *this;
return Cstrcmp( pThisCStr, Cstr );
}
/**
Append a character to the end of "this" string
*/
inline
String &String::operator +=(const char ch)
{
char buf[4];
buf[0] = ch;
buf[1] = '\0';
*this += buf; // call String += Cstr
return *this;
} // operator +=
/**
* operator +: String("abcd") + " efgh"
*
Create a new String object which contains the concatenation of the
two operand strings (self and the argument "s"). The reference
count for the new String object will be zero. If it is assigned
then it will be incremented.
Example:
String a("abcd");
String b = a + " efgh";
When these statements are executed String 'a' will contain "abcd"
and String 'b' will contain "abcd efgh". The reference count
for both String objects will be 1.
*/
inline
String String::operator +( const char *Cstr )
{
String tmp;
tmp.appendString( *this );
tmp.appendCString( Cstr );
return tmp;
} // operator +
/**
* operator +: String("abcd") + String(" efgh")
*
*/
inline
String String::operator +(String &s)
{
String tmp;
tmp.newSharedData();
tmp.appendString( *this );
tmp.appendString( s );
return tmp;
} // operator +
/**
* operator+ : String + ch
*
Append a String and a character.
*/
inline
String String::operator +(const char ch)
{
char buf[4];
buf[0] = ch;
buf[1] = '\0';
String tmp;
tmp = *this + buf; // call String + Cstr
return tmp;
} // operator +
/**
strlen() returns the number of characters in the string,
not including the null character which terminates the
String.
*/
inline
size_t String::strlen()
{
int len = value->data.length();
if (len > 0 && value->data[len-1] == '\0')
len--;
return len;
}
#endif
string/StringTest.C 0100600 0000764 0000764 00000071061 07733211532 013235 0 ustar iank iank
/** \file
Regression and verification code for the String class. No errors
should be printed when this code runs.
You really need to run these regression tests before using this
code on your system. This code has been tested on a variety of
platforms. Although C++ compilers are getting more standard
conformant, this the String class uses C++ features which
historically been supported differently by different compilers.
Note that the existence of these regression tests does not change
that statement this code is provided without waranty. You
use this code at your own risk.
I also recommend verifying this code using Purify or a similar
memory usage verification tool. This level of pain and difficulty
should not be necessary, but hey, its C++.
In an earlier version of the String class the SubString class was
included in the same .h file, with some function implementations
in String.C.
This version separates the two classes. A set of regression tests
(SubStrTest.C) was written for the SubString class. However, for
historical reasons, this file also include some tests for
SubString, which overlap with the tests in SubStrTest.C.
Compiling this code:
This code is written to compile on Microsoft Visual C++ 6.0 and
higher. It will also compile on GNU C++ and Sun's 6.0 and later
C++ compilers. To compile under Microsoft VC++:
cl -Zi -TP -GX SubString.C String.C StringTest.C -o StringTest
The -GX flag is needed to support exceptions, which are used
rather than the assert() functions in the earlier versions.
Copyright and Use
You may use this source code without limitation and without
fee as long as you include:
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001.
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003.
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
/** POSIX string operations */
#include
String a("abcdefg");
String b = a(0,4);
The string() function sections the SubString, resulting in a String.
*/
SubString::operator String() const
{
return this->string();
}
/**
Check that start and len are within the bounds of the String
argument s.
*/
void SubString::rangeCheck(String &s, const size_t start, const size_t len)
throw(std::out_of_range)
{
char *errMsg = 0;
bool rangeError = false;
size_t sLen = s.strlen();
if (start >= sLen) {
errMsg = "SubString: start >= String length";
rangeError = true;
}
else if (start + len > sLen) {
errMsg = "SubString: start + len >= String length";
rangeError = true;
}
if (rangeError) {
throw std::out_of_range( errMsg );
}
} // rangeCheck
/**
This constructor initializes the SubString object with a
reference to a String object, a start index for the SubString
section and a length. For example:
String s = "max is a cat";
SubString t(s, 9, 3);
Except for initialization, nothing happens in the SubString
constructor. It is only when a SubString is assigned or otherwise
operated on that a new String is created (the region of the
initialization String defined by the start and length).
SubStrings are commonly created by the String class. When creating
a SubString object the String class initializes the SubString with
itself, and with the starting index and length. The code below
shows an example of an sub-string expression created by the String
class.
String a("abcd12345jkl");
String b = a(4, 5);
The first argument is the starting index (where Strings start
with index 0). The second argument is the length.
This class throws the std::out_of_range error when either the start
or (start+len) are out of range (beyond the end of the String).
Exceptions are described in The C++ Programming Language, Third
Edition by Stroustrup, section 14.10.
One of the corner cases that must be considered is a SubString
of an empty String:
String empty; // empty string
String empty2 = empty(0, 0); // This will throw an exception
Ok, so we're taking a zero length section of a String starting at
zero. Is that so wrong? I don't know about so wrong, but
it does seem incorrect. The rules with with a SubString is that
the starting index must be less than the String length. In this
case the starting index is equal to the String length. As a result
this example would throw an exception.
A SubString can be thought of as copy on demand. The SubString
expression will not actually section the String until the
SubString is assigned, either explicitly or implicitly.
The SubString will not alter the reference count of the String
that it references.
*/
SubString::SubString( String &s, const size_t start, const size_t len )
throw(std::out_of_range)
: value( new SharedData() )
{
try {
rangeCheck(s, start, len);
size_t sLen = s.strlen();
value->pStr( &s );
value->start( start );
value->subStrLen(len);
}
catch (std::out_of_range e) {
throw e;
}
} // SubString constructor
/**
A SubString defines a region in a String. When an assignment is
made to a SubString and the length of the data being assigned is
not the same as the SubString length, a copy space must be
created.
In the case where the length of the data being copied is larger
than the SubString length, the area defined by the SubString must
be made larger. This extends the length of the string and moves
characters above the region to higher indices.
In the case where the length of the data being copied is less
than the SubString length, the area defined by the SubString
must be reduced. This reduces the length of the String and
results in moving characters above the copy region to lower
indices.
This function allows assignment code to simply copy data into
the assignment region. Note that both the write() and resize()
functions will create an unshared String if the String is shared.
*/
void SubString::createCopySpace(size_t start, size_t charsToCopy)
{
size_t subStrLen = value->subStrLen();
if (charsToCopy != subStrLen) {
size_t strlen = value->pStr()->strlen();
if (charsToCopy < subStrLen) {
// The end of the copy region will be start+charsToCopy
// The end of the SubString section is start+subStrLen
size_t dest = start+charsToCopy;
for (size_t i = start+subStrLen; i < strlen; i++, dest++) {
char ch = value->pStr()->read(i);
value->pStr()->write(dest, ch );
}
size_t shrink = subStrLen - charsToCopy;
size_t newLen = strlen - shrink;
value->pStr()->resize( newLen );
}
else if (charsToCopy > subStrLen) {
size_t expand = charsToCopy - subStrLen;
size_t newLen = strlen + expand;
value->pStr()->resize( newLen );
size_t dest = newLen-1;
for (int i = strlen-1; i >= (int)start; i--, dest--) {
char ch = value->pStr()->read(i);
value->pStr()->write(dest, ch );
}
}
}
} // createCopySpace
/**
String = SubString + Cstr
*/
String SubString::operator +(const char *Cstr)
{
String result = *this;
if (Cstr != 0) {
result += Cstr;
}
return result;
}
/**
String = SubString + SubString
*/
String SubString::operator +(const SubString& sub)
{
String s1 = *this;
String s2 = sub;
String result = s1 + s2;
return result;
}
/**
String = SubString + String
*/
String SubString::operator +(const String &str)
{
String s1 = *this;
String result = s1 + str;
return result;
}
/**
operator =: This function assigns a C character string to a
SubString. For example:
String foo("abcdefghijkl");
String bar = foo;
foo(4, 4) = "1234";
After the SubString assignment is executed, the String "foo" will
contain "abcd1234ijkl". Note that the contents of bar will
be unchanged. However, the reference count of bar will be
1.
A smaller string can be assigned to a larger string:
foo(4, 4) = "12";
will result in "abcd12ijkl" (note that in this case a length
4 string ("efgh") was replaced by a length 2 string ("12"))
The symetric case, where a longer string is assigned to a
smaller string:
foo(4, 4) = "123456
Results in a longer String, as the length 4 string is replaced by
a length 6 string: "abcd123456ijkl".
Assignment to a SubString results in a String. The String is the
String that was used to initialize the SubString constructor,
modified by the assignment. The assignment
foo(4, 4) = "1234";
can be broken up into two steps:
SubString t1(foo, 4, 4);
String t2 = (t1 = "1234");
String a("the quick brown fox jumped");
String b("dog");
a(16, 3) = b;
This function does the same thing as the C-string version
of operator=. Please see the comment associated with this
function.
*/
String SubString::operator =( String& str )
{
size_t charsToCopy;
if ((charsToCopy = str.strlen()) > 0) {
size_t start = value->start();
// create a copy region that is the correct size
createCopySpace(start, charsToCopy );
if (value->pStr()) {
size_t i, cnt;
for (i = start, cnt = 0; i < start + charsToCopy; i++,cnt++) {
value->pStr()->write(i, str[cnt]);
}
}
}
if (value->pStr())
return *(value->pStr());
else {
String empty;
return empty;
}
} // operator = (String)
/**
* operator = (SubStr &)
*
For example:
a(0, 4) = b(4, 4);
*/
String SubString::operator =(const SubString& str )
{
// convert to a SubStr = String
*this = str.string();
return (*this).string();
} // operator=
/**
Return the String section defined by the SubString as a
String result.
A SubString object consists of a String and a range (e.g., start
index and length) which defines a string section. In a sense, a
SubString object is intent, not action. The string() function
performs the action by sectioning the SubString into a String.
No bounds check is made here (for example, to assure that
subStrEnd is less than sLen) because this check is made
when the SubString is constructed.
*/
String SubString::string() const
{
String retVal;
if (value->pStr()) {
size_t sLen = value->pStr()->strlen();
size_t start = value->start();
size_t subStrLen = value->subStrLen();
size_t subStrEnd = start + subStrLen;
for (size_t i = start; i < subStrEnd; i++) {
retVal += (*(value->pStr()))[i];
}
}
return retVal;
} // SubString::string
/**
Compare the SubString to a C string (e.g., const char *). The function
returns:
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001, 2002, 2003
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include "RCObject.h"
#include "RCPtr.h"
class String;
/**
This class supports the creation and manipulation of SubStrings,
which are sections of Strings. SubStrings are usually created by
the "()" operator of the String class. As a result, this class
and the String class are closely related.
The String class "()" operator takes two numeric arguments, the
starting index and the length (String objects follow C/C++ indexing
and start at 0).
Examples:
String a("abcdefgh");
String b;
String tmp;
b = a(0,4); -- assignment of SubString to String
a(0, 4) = "0123"; -- assignment of CString to SubString
a(0, 4) = a(4, 4); -- assignment of SubString to SubString
a(0, 4) = b; -- assignment of String to SubString
Although a SubString is usually created by the String "()"
operator, it can also be created explicitly. For example:
String s = "the lazy dog";
SubString t(s, 9, 3);
t = "golden";
std::cout << (const char *)s << std::endl;
Note that a SubString is a reference to a String. So the
assignment to the SubString alters the String. The example
above will write "the lazy golden" to std::cout.
The SubString class suports relational operators. This is faster
than converting the SubString to a String and performing the
relational operation on String operands.
The SubString class does not support an argumentless constructor.
This assures that all SubString objects are properly initialized
with a String and a range (start index and length). The starting
index and length are checked in the constructor to assure that they
are within the bounds of the String argument. If they are not,
an std::out_of_range exception will be thrown.
There is no type cast supported for SubString to const char *
although it might be nice to be able to do the following
String a = "morgan dreams of being fat";
const char *sleep = a(0, 13); // Not supported!
Unfortunately, there is no practical way to implement this. The
right-hand-side operation a(0, 13) creates a compiler
generated String temporary. This temporary will be
deallocated after the statement containing the assignment.
If the memory for the temporary were referenced from the
const char *, the const char * pointer would
point to deallocated memory.
There are some corner cases that I could not figure out how to
protect against. For example, it is possible to create a SubString
object that references a deallocated string. This is shown in the
function makeSubString below:
//
// Don't do this!
// This function will result in a SubString that references
// a String which has been deallocated.
//
SubString makeSubString(const char *Cstr, size_t start, size_t len)
{
String s( Cstr );
SubString t(s, start, len);
return t; // The String "s" will be deallocated on exit
}
For this function to work, the SubString constructor would have to
increment the reference count of the String, so that the String
would not be deallocated on exit (when its reference count reaches
zero). This could be done if an actual String, rather than a
pointer to a String were used in the SubString shared data.
Implementing the SubString in this way break the functionality of
the SubString object. Consider the code below:
String a = "12345678";
a(3,0) = "abcd";
If the SubString shared data contained a String, rather than a
pointer to a string, the constructor for the SubString would
increment the reference count for a to 2. This would work
well for the makeSubString function, but would break the
assignment above.
If the SubString shared data contained a String, the assignment to
this String would result in a unique copy being be made, since the
String is shared (e.g., reference count > 1). The C-string "abcd"
would be inserted into the String in the SubString shared data
(which is now unique). The result will be that "a" is unchanged
and the String we have assigned into will be deallocated when the
SubString destructor is executed.
In contrast to the String class, only a few SubString functions are
inlined. Many SubString functions reference the String class. The
String class is defined only as an abstract class here. This is
required by the fact that the String class also includes
SubString.h.
\author Ian Kaplan www.bearcave.com
*/
class SubString
{
private:
class SharedData : public RCObject
{
private:
String *pStr_;
size_t start_;
size_t subStrLen_;
private:
void copy(const SharedData &rhs);
public:
SharedData();
SharedData(const SharedData &rhs) { copy( rhs ); }
~SharedData() {}
void pStr( String *strRef ) { pStr_ = strRef; }
String *pStr() { return pStr_; }
void start(int start) { start_ = static_cast
cl -Zi -TP -GX SubString.C String.C SubStrTest.C -o SubStrTest
The -GX flag is needed to support exceptions, which are used
rather than the assert() functions in the earlier versions.
Copyright and Use
You may use this source code without limitation and without
fee as long as you include:
This software was written and is copyrighted by Ian Kaplan, Bear
Products International, www.bearcave.com, 2001.
This software is provided "as is", without any warrenty or
claim as to its usefulness. Anyone who uses this source code
uses it at their own risk. Nor is any support provided by
Ian Kaplan and Bear Products International.
Please send any bug fixes or suggested source changes to:
iank@bearcave.com
*/
#include
String s = "Mr. Golden Noodle";
String pasta = s(11, 6);
were 11 is the start index and 6 is the length.
The construction of a SubString should always result in a correct
SubString. This means that the start index should be within the
bounds of the String and the start index plus the size should be in
the bounds as well. If this is not the case, the SubString
constructor will throw an exception.
So, what about:
String empty;
String empty2 = empty(0, 0);
Ok, so we're taking a zero length section of a String starting at
zero. Is that so wrong? I don't know about so wrong, but
it does seem incorrect. The rules with with a SubString is that
the starting index must be less than the String length. In this
case the starting index is equal to the String length.
*/
bool subStrSection()
{
printf("Test SubString section operations\n");
bool rslt = true;
String a("abcdefghij");
String d = a;
// a(0,5) means start at index 0, length of the section is 5
String subA = a(0,5);
if (subA.strlen() != 5) {
printf("1. SubString length = %d, should be 5\n", subA.strlen() );
rslt = false;
}
// make sure that the reference count for the original string
// is not affected. Note that a right hand size SubString
// operation does not affect the String on which the SubString
// is defined.
if (d.getRefCnt() != 2) {
printf("2. d.getRefCnt() = %d, should be 2\n", d.getRefCnt() );
}
String b = subA;
if (b != "abcde") {
printf("3. SubString section is incorrect. ");
const char *Cstr = b;
printf("SubStr = %s, should be \"abcde\"\n", Cstr );
rslt = false;
}
String c = a(5,5);
if (c != "fghij") {
printf("4. SubString section is incorrect. ");
const char *Cstr = b;
printf("SubStr = %s, should be \"abcde\"\n", Cstr );
rslt = false;
}
if (a.getRefCnt() != 2) {
printf("5. a.getRefCnt() = %d, should be 2\n", a.getRefCnt() );
rslt = false;
}
bool exceptionTest1 = false;
// Test exceptions on an out of bound reference
try {
// use a length (8) that places the section beyond the end
// of the String.
String x = a(5, 8);
}
catch (std::out_of_range e) {
exceptionTest1 = true;
}
if (!exceptionTest1) {
printf("6. There should have been a SubString construction exception\n");
rslt = false;
}
bool exceptionTest2 = false;
try {
// use a start that is beyond the end of the String
String x = a(10, 1);
}
catch (std::out_of_range e) {
exceptionTest2 = true;
}
if (!exceptionTest2) {
printf("7. There should have been a SubString construction exception\n");
rslt = false;
}
String empty;
if (empty.strlen() != 0) {
printf("8. length of an empty string is wrong\n");
rslt = false;
}
bool exceptionTest3 = false;
String empty2;
try {
empty2 = empty(0,0);
}
catch(std::out_of_range e) {
exceptionTest3 = true;
}
if (!exceptionTest3) {
printf("9. There should have been a SubString construction exception\n");
rslt = false;
}
if (empty2.strlen() != 0) {
printf("10. length of empty2 is wrong\n");
rslt = false;
}
if (empty2.getRefCnt() != 1) {
printf("11. reference count of empty2 is wrong\n");
rslt = false;
}
String e = "12345678";
String f = e;
e(4,0) = "abcd";
if (e != "1234abcd5678") {
printf("12. insert, via a zero length section, failed\n");
size_t len = e.strlen();
printf("12. e.strlen() = %d\n", len );
if (len > 0) {
printf("12. e = %s\n", (const char *)e );
}
}
if (e.strlen() != 12) {
printf("13. e.strlen() = %d, should be 12\n", e.strlen() );
}
if (f != "12345678") {
printf("14. improperly altered string in 'f'\n");
}
if (f.getRefCnt() != 1 && e.getRefCnt() != 1) {
printf("15. reference counts are wrong\n");
}
return rslt;
} // subStrSection
/**
Test assignment to and from a SubString object
*/
bool subStrAssign()
{
bool rslt = true;
printf("Test assignment to and from a SubString object\n");
const char *CStr1 = "abcde1234jklm";
const char *CStr2 = "abcdefghijklm";
String a = CStr1;
if (a != CStr1) {
printf("1. Contents of String is incorrect\n");
rslt = false;
}
String b = a;
if (b.getRefCnt() != 2) {
printf("2. Reference count is incorrect\n");
rslt = false;
}
a(5, 4) = "fghi";
if (a != CStr2) {
printf("3. Contents of String is incorrect\n");
rslt = false;
}
if (b.getRefCnt() != 1) {
printf("4. Reference count is incorrect\n");
rslt = false;
}
String c = a(0, 5);
if (c != "abcde") {
printf("5. Contents of String is incorrect\n");
rslt = false;
}
// assign a string to a substring section
a(5, 5) = c;
if (a(0,10) != "abcdeabcde") {
printf("6. Contents of String is incorrect\n");
rslt = false;
}
// Now check assignment to regions that are not the same
// length as the string being assigned.
String target = "the lazy golden";
String t2 = target;
// assign a longer C-string to a shorter assignment region
t2(0, 3) = "morgie is a";
if (t2 != "morgie is a lazy golden") {
printf("7. C-string assignment to SubString is wrong\n");
rslt = false;
}
if (target.getRefCnt() != 1) {
printf("8. Reference count is wrong\n");
rslt = false;
}
if (target != "the lazy golden") {
printf("9. The contents of target is wrong\n");
rslt = false;
}
t2(17, 6) = "spaniel";
// assign a shorter C-string to a longer assignment region
t2(0, 6) = "max";
if (t2 != "max is a lazy spaniel") {
const char *Cstr = t2;
printf("10. The contents of t2 is incorrect. t2 = %s\n", Cstr );
rslt = false;
}
// Test assignments of String to SubString
// Longer assigned to shorter
target = "max is a dog";
String replaceAnimal = "people";
String replaceName = "Maggie";
target(9,3) = replaceAnimal;
target(0,3) = replaceName;
if (target != "Maggie is a people") {
printf("11. String to SubString assignment failed\n");
rslt = false;
}
// shorter assignments to longer SubString region
replaceName = "Ian";
target(0, 6) = replaceName;
if (target != "Ian is a people") {
printf("11. String to SubString assignment failed\n");
rslt = false;
}
if (target.strlen() != strlen("Ian is a people")) {
printf("12. length of the string is wrong\n");
rslt = false;
}
// Test the explicit SubString constructor with assignment.
// Also make sure that chained assignment works in this case.
const char *expected_result = "the lazy golden";
String u;
String s = "the lazy dog";
SubString t(s, 9, 3);
u = t = "golden";
if (s != expected_result) {
printf("13. the contents of s is \"%s\", should be \"%s\"\n",
(const char *)s, expected_result);
rslt = false;
}
if (u != expected_result) {
printf("14. the contents of u is \"%s\", should be \"%s\"\n",
(const char *)u, expected_result);
rslt = false;
}
// The result of the assignment to the SubString should be the
// String, "s" passed to the constructor. This is assigned to
// the String u, so the reference count should be 2.
if (u.getRefCnt() != 2) {
printf("15. reference count is %d, should be 2\n", u.getRefCnt() );
rslt = false;
}
return rslt;
} // subStrAssign
/**
Test SubString relational operations
*/
bool subStrRelations()
{
bool rslt = true;
String a = "abcdeabcde";
printf("Test SubString relational operators\n");
//
// == operator
//
// SubString != SubString
bool t1 = false;
if (a(0,5) == a(5, 5)) {
t1 = true;
}
if (!t1) {
printf("1. SubString == SubString failed\n");
rslt = false;
}
// SubString == Cstr
bool t2 = false;
if (a(0,5) == "abcde") {
t2 = true;
}
if (!t2) {
printf("2. SubString == Cstr failed\n");
rslt = false;
}
// SubString == String
bool t3 = false;
String b = "abcde";
if (a(0,5) == b) {
t3 = true;
}
if (!t3) {
printf("3. SubString == String failed\n");
rslt = false;
}
t1 = true;
if (a(1,4) == "bcd") {
t1 = false;
}
if (!t1) {
printf("4. SubString == CStr of unequal length should have been false\n");
rslt = false;
}
//
// != operator
//
t1 = false;
t2 = false;
t3 = false;
// 012345678901234
String c = "abcdexyzzyabcde";
// SubString != SubString
if (a(0,5) != c(5,5)) {
t1 = true;
}
if (!t1) {
printf("5. SubString != SubString failed\n");
rslt = false;
}
// SubString != Cstr
if (a(0,5) != "xyzzy") {
t2 = true;
}
if (!t2) {
printf("6. SubString != SubString failed\n");
rslt = false;
}
// SubString != String
b = "zyzzy";
if (a(0,5) != b) {
t3 = true;
}
if (!t3) {
printf("7. SubString != String failed\n");
rslt = false;
}
bool t4 = false;
if (a(0, 5) != "abcd") {
t4 = true;
}
if (!t4) {
printf("8. SubString != CStr of unequal length failed\n");
rslt = false;
}
t4 = true;
if (a(1, 4) != "bcde") {
t4 = false;
}
if (!t4) {
printf("9. SubString != CStr are equal - test should be false\n");
rslt = false;
}
//
// < operator
//
t1 = false;
t2 = false;
t3 = false;
t4 = false;
// SubString < SubString
if (a(1,5) < c(5,5)) {
t1 = true;
}
if (!t1) {
printf("10. SubString < SubString failed\n");
rslt = false;
}
// SubString < SubString: overlapping SubString sections on the same String
t1 = false;
if (c(1,5) < c(5,5)) {
t1 = true;
}
if (!t1) {
printf("11. SubString < SubString failed for overlapping sections\n");
rslt = false;
}
// SubString < Cstr
t1 = false;
if (c(1,5) < "pdsrsmzf") {
t1 = true;
}
if (!t1) {
printf("12. SubString < Cstr \n");
rslt = false;
}
// SubString < String
// note that b is still "xyzzy"
t1 = false;
if (c(0,8) < b) {
t1 = true;
}
if (!t1) {
printf("13. SubString < String \n");
rslt = false;
}
//
// > operator
//
// SubString > SubString
// 0123456789
String d = " abc ";
t1 = false;
if (c(10, 5) > d(2, 5)) {
t1 = true;
}
if (!t1) {
printf("14. SubString > SubString\n");
rslt = false;
}
// SubString > Cstr (note, "xyz" will be "extended" with null chars)
t1 = false;
if (c(5, 5) > "xyz") {
t1 = true;
}
if (!t1) {
printf("15. SubString > Cstr\n");
rslt = false;
}
// SubString > String
b = "lesser of two evils";
t1 = false;
if (c(5, 5) > b) {
t1 = true;
}
if (!t1) {
printf("16. SubString > String\n");
String ts = c(0, 5);
const char *t1 = ts;
const char *t2 = b;
printf("c(0, 5) = %s, b = %s\n", t1, t2);
rslt = false;
}
//
// >= operator
//
t1 = false;
if (c(0,5) >= c(10, 5)) {
t1 = true;
}
if (!t1) {
printf("17. SubString >= SubString\n");
rslt = false;
}
t1 = false;
if (c(0,5) >= "abcde") {
t1 = true;
}
if (!t1) {
printf("18. SubString >= CStr\n");
rslt = false;
}
t1 = false;
if (c(5,5) >= "abcde") {
t1 = true;
}
if (!t1) {
printf("19. SubString >= CStr\n");
rslt = false;
}
t1 = false;
b = "abcde";
if (c(0,5) >= b) {
t1 = true;
}
if (!t1) {
printf("20. SubString >= String\n");
rslt = false;
}
//
// <= operator
//
t1 = false;
if (c(0,5) <= c(5, 5)) {
t1 = true;
}
if (!t1) {
printf("21. SubString <= SubString\n");
rslt = false;
}
t1 = false;
if (c(0,5) <= "xyzzy") {
t1 = true;
}
if (!t1) {
printf("22. SubString <= CStr\n");
rslt = false;
}
t1 = false;
b = "xyzzy";
if (c(0,5) <= b) {
t1 = true;
}
if (!t1) {
printf("23. SubString >= String\n");
rslt = false;
}
t1 = false;
b = "abcde";
if (c(0,5) <= b) {
t1 = true;
}
if (!t1) {
printf("24. SubString >= String\n");
rslt = false;
}
return rslt;
} // subStrRelations
/**
Test for other types on the LHS of a relation. This
must be supported by global operators.
*/
bool globalSubStrRelations()
{
printf("Test global SubString relational operators\n");
bool rslt = true;
// 012345678901234
String a = "abcdexyzzyabcde";
// Cstr == SubString
bool t1 = false;
if ("abcde" == a(0, 5)) {
t1 = true;
}
if (!t1) {
printf("1. Cstr == SubString\n");
rslt = false;
}
// Cstr != SubString
t1 = false;
if ("abcde" != a(5, 5)) {
t1 = true;
}
if (!t1) {
printf("2. Cstr != SubString\n");
rslt = false;
}
// Cstr < SubString
t1 = false;
if ("abcde" < a(5, 5)) {
t1 = true;
}
if (!t1) {
printf("3. Cstr < SubString\n");
rslt = false;
}
// Cstr > SubString
t1 = false;
if ("xyzzy" > a(0, 5)) {
t1 = true;
}
if (!t1) {
printf("4. Cstr > SubString\n");
rslt = false;
}
// Cstr >= SubString
t1 = false;
if ("abcde" >= a(0, 5)) {
t1 = true;
}
if (!t1) {
printf("5. Cstr >= SubString\n");
rslt = false;
}
// Cstr >= SubString
t1 = false;
if ("xyzzy" >= a(0, 5)) {
t1 = true;
}
if (!t1) {
printf("6. Cstr >= SubString\n");
rslt = false;
}
// Cstr <= SubString
t1 = false;
if ("abcde" <= a(0, 5)) {
t1 = true;
}
if (!t1) {
printf("7. Cstr <= SubString\n");
rslt = false;
}
// Cstr <= SubString
t1 = false;
if ("abcde" <= a(5, 5)) {
t1 = true;
}
if (!t1) {
printf("8. Cstr <= SubString\n");
rslt = false;
}
return rslt;
} // globalSubStrRelations
int
main()
{
bool passed = true;
printf("SubString tests \n");
if (!subStrRefCnt()) {
passed = false;
}
if (!subStrSection()) {
passed = false;
}
if (!subStrAssign()) {
passed = false;
}
if (!subStrRelations()) {
passed = false;
}
if (! globalSubStrRelations()) {
passed = false;
}
printf("Tests ");
if (passed)
printf("passed\n");
else
printf("failed\n");
return 0;
}