Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Examining RogueWave's Tools.h++


FEB96: Examining RogueWave's Tools.h++

Perry is a senior systems analyst for Arco Alaska and can be contacted at [email protected].


A while back I wrote a fairly complex system for storing and retrieving petroleum-reservoir simulation X/Y plot output. Using only a compiler, it took me over a year to develop the application, which consisted of about 30,000 lines of C++ code.

When the time came to port the program to another platform, a fellow developer introduced me to RogueWave's Tools.h++ foundation-class library. Surprisingly, it only took me three months to write the new version, which was only 6000 lines long. Furthermore, the run-time speed of the app increased by more than an order of magnitude.

This project made me a believer in good foundation-class libraries. Luckily, C++ programmers have a variety of options, including STL, MFC, Booch, and RogueWave. In this article, I'll examine RogueWave's Tools.h++, the cornerstone for all of my C++ work since that first successful project.

RogueWave Tools.h++ Overview

RogueWave's Tools.h++ is a C++ class library consisting of more than 100 C++ classes, including those for time, dates, strings, linked lists, and other fundamental structures. Other classes support virtual streams, string and character manipulation, file management, regular expressions, tokenizers, virtual-page and buffered-page heaps, buffered-disk page heaps, timers, benchmarks, templates, bit vectors, cache managers, error handling, iterators, and more. The classes can be run as a DLL, allowing smaller

executables and code sharing.

Tools.h++ also includes a complete set of Smalltalk-like collection classes. Class Set, for instance, can be used to collect a group of screen windows that will need to be refreshed, eliminating redundant refreshes. All classes support persistence, allowing objects to be saved to disk and restored later. Multiple pointers to the same object can also be restored. All classes are extensible so that you can create your own custom classes.

The class library is compatible with most compilers and platforms, including Windows 3.x, Windows 95, Windows NT, MS-DOS, OS/2, Macintosh, Sun/SunOS, Solaris, IBM RS/6000/AIX, Silicon Graphics Iris/IRIX, HP/HP-UX, DG/DG/UX, UNIX System V, and even Linux.

String Support

String manipulation is an area where class libraries can shine, and RogueWave's RWCString class family is no exception. In addition to the expected behaviors for strings (dynamic resizing, concatenation operators, and so on), RWCString is multithread safe and supports multibyte character sets. It uses "copy on write" methods during copy construction for higher efficiency. "Copy on write" maintains reference counts to a single instance until copying is forced by a change to one of the referenced instances.

RWCString is typically one of the first classes new Tools.h++ users become familiar with. In Listing One, for example, Operator() has been overloaded to accept a regular-expression argument. The operator returns a reference to a substring (RWCSubString, cousin of RWCString) whose extent is the segment "34.5". The assignment operator "=" is used to replace segment "34.5" with segment "184.9". This syntax is elegant and cleverly designed.

Note that in Listing Two, RWCTokenizer does not alter the string being tokenized. This behavior is entirely different from the standard strtok, which deposits NULLs between every token in the tokenized string. Note also that RWCStrings are aware of streambuf and virtual stream I/O. RogueWave gives all of its foundation classes this ability. RWWString, the multibyte analog of RWCString, contains much of the functionality of RWCString, but understands wchar_t (wide char*) character units.

Date/Time Handling

If you have ever written (or maintained) time/date functions based upon time.h, you will appreciate Tools.h++'s time and date functionality. Classes such as RWDate and RWTime let you program at the level of a Visual Basic programmer for such standard concepts as date and time. Listing Three illustrates how you can use these two classes. As Listing Four shows, RogueWave has also gone a long way toward supporting internationalization.

The only thing I've struggled with concerning RWTime is "4294967295," the largest unsigned long. One of RWTime's private members is the number of seconds since 1/1/1901 UTC. Given that "02/05/2037 21:18:15" is exactly 4,294,967,295 seconds from 1/1/1901, you have discovered an upper time bound that you should be aware of if you need dates past 2037. There are many obvious workarounds, but I nonetheless forgot about it--and users discovered it for me.

Template Classes

Templates are parametrized collection classes, where the parameter is the type of the object being collected. Tools.h++ templates come in three flavors: intrusive lists, value-based collections, and pointer-based collections. Most of my recent development work has been with the pointer and value template classes. The stricter class typing inherent in template-based programming offers significant advantages over the older polymorphic-inheritance approaches. Templates greatly simplify complex data-structure manipulation.

In general, value-based collections offer easier syntax (less pointer manipulation and manual free-store destruction) at the expense of decreased performance during insertions and deletions. The performance issue is related to the fact that value-based collections make copies of inserted items rather than simply referencing the pointer.

I typically use value-based templates on small data structures of fairly constant size, "template-ready" classes from RogueWave (RWCString or RWDate, for instance), or built-in types like floats or ints. When the data-structure complexity warrants, or when the collection is frequently updated through resizing or insertion, I generally pick a pointer-based template.

Consider the data structure in Listing Five, which could represent a point on an X/Y curve and a textual description of the point. In order to collect this XYPoint structure in a Tools.h++ template, you must extend the data structure so that the semantics of the structure can be supported by the template. Clearly, these XYPoints should be stored in a sorted collection. The sort order should be determined by the xDate member. We must also know when two items are "equal." In addition, copy construction, default construction, and assignment semantics should be defined; see Listing Six. The class XYPoint is now ready to be used with nearly any of the Tools.h++ pointer or value templates. In short, the templates features in Table 1 are automatically available when you use Tools.h++ templates. Listing Seven illustrates both pointer- and value-sorted vector template usage.

All expected behavior for collections with the exception of object persistence is well covered by Tools.h++ templates. Templates in both the pointer and value variety include hash dictionaries, sorted vectors, ordered vectors, singly and doubly linked lists, hash sets (uniqueness enforced), stacks, and queues. For those few compilers that do not yet support templates or support them incompletely, Tools.h++ provides "generic" collection classes, similar to templates but less efficient. These use some of the macro-based parametrized types described by Stroustrup. Few users will need these classes, but they may come in handy for the occasional compiler incompatibility problems that may come up during RogueWave installation. Gnu's g++ has had some historical compatibility problems with Tools.h++, so future releases of g++ may require the generic pseudotemplates.

Adding Persistence

Persistence is not built into the Tools.h++ templates, but RogueWave makes it easy to add object persistence by providing the RWvistream and RWvostream abstract base classes. These classes capitalize on the considerable strengths of the iostream model, but extend the functionality for arbitrary binary data and abstraction of byte sources and sinks. Tools.h++ provides its own instantiable ASCII and binary veneer over the streambuf class (RWp[io]stream and RWb[io]stream, respectively).

The beauty of virtual streams is the power that the generalization of data sources and sinks implies. When you send a class to a RWvostream, you are effectively sending it without concern for its storage format within the stream pipeline. This opens up all sorts of possibilities, such as using object persistence over network protocols like Berkeley sockets. You could write your object to a socket-based child of RWvostream, whereupon it could be received and read across the network by another socket-based child of RWvistream. The implementation of the streaming operators to accomplish this behavior is invariant across the choice of storage media. In other words, the same streaming functions can be used to store a class onto a network or an ASCII file. I've implemented such a scheme using Tools.h++'s RWbistream and RWbostream by adding virtual stream capabilities to a class of my own design. Then, I constructed bistreams and bostreams on either end of an interprocess pipeline. I could then simply send the binary data as a class through the pipeline, whereupon it could be caught at the other end of the pipeline in exactly the state we had sent it. The result is elegant: Instead of sending a stream of bytes that must be reparsed on the other end, we send an entire object capable of reconstructing itself on the other side. The reconstructed object retains all of its original methods and properties.

In another recent engineering project, we discovered that an application of ours had become I/O bound. This program performed a series of material balance calculations on 600 multiwell patterns for the Kuparuk River Unit, the second largest oil field in the United States. It was too time consuming for the engineers to request the data from a relational database because the data required for the material-balance calculations was extremely hierarchical. We decided, therefore, to implement a persistent RogueWave class capable of being written to and read from a RogueWave virtual stream. This class encapsulated the entire time-valued behavior of a material-balance pattern, including the cumulative fluid amounts, reservoir-volume conversion factors, and initial properties of the pattern. The Oracle access speed per pattern was about 30 seconds. With Tools.h++ virtual streams, we were able to cut access time to a fraction of a second. The Oracle data was processed at night in a huge batch process into the RogueWave object database. In the morning, the engineers were able to rapidly flip through the patterns and make decisions at a much faster rate.

Listing Eight extends the XYPoint class to be "virtual-stream aware." Since RWDate and RWCString are already virtual-stream aware, this task becomes trivial.

These functions store the individual class you are collecting, but what about the collection itself? How do you implement persistence for a Tools.h++ template collection? For simple vectors, you must store only the integer number of elements followed by the individual members of the collection. This is not a preferred method due to the loss of objectmorphology. However, if you purchase the source code from RogueWave, you can build children of the template classes which contain some of the persistence methods of the Smalltalk collectable classes. I've implemented a child of RWTPtrSortedVector with some basic persistence capabilities without much trouble.

Smalltalk-like Collection Classes

If you use polymorphic inheritance trees in your class design, you will not be disappointed with RogueWave's implementation of its Smalltalk-80-like Collection Classes. Any object of this variety must ultimately be derived from the RWCollectable abstract base class, the root of the inheritance tree. Although the trend seems to be moving away from this variety of class design and toward templates, this approach has distinct advantages. The programming interface is a bit cleaner. There is an aesthetic "goodness" in the fact that inheritance trees reuse object code rather than source code (as templates do). Tools.h++ also provides elegant, more-complete persistence methods for all children of RWCollectable. The persistence methods alone make RWCollectable worthwhile for projects that store and retrieve objects from files other than traditional databases.

The methods for making a class RWCollectable are similar to those for making it compatible with Tools.h++ templates, but more extensive. The usual methods for equality, comparison, construction, and destruction must be defined. RWCollectable classes must additionally redefine virtual persistence functions restoreGuts and saveGuts. These functions have been overloaded for both RWFile (a file I/O veneer class) and RWv[io]stream classes. An isA() function must be defined to return the type of an RWCollectable child. This allows you to identify object types during ambiguous moments such as construction of collectibles whose type is unknown until run time. The weak typing of such class design can lead to the usual casting problems of all inheritance trees, but being able to store several types of RWCollectable classes within common containers such as RWSortedVector and RWBTreeDictionary is compensation enough.

Object-Database Management

The strong suit of the Smalltalk Tools.h++ classes is their advanced persistence mechanisms. However, a database must know how to organize multiple objects on a disk all at once and be able to retrieve them rapidly. RWFileManager, child of RWFile, performs such a function by maintaining a linked list of free-space blocks within a disk file. Requests can be made to RWFileManager for just enough space to store a given class instance. RWCollectable classes can return the space required to store themselves with member function binaryStoreSize(). RWBTreeOnDisk maintains a B-tree on the disk file. The disk-based B-tree is used in conjunction with RWFileManager to associate a name (const char*) with each object stored with RWFileManager.

The name can be used later to retrieve the object from the disk file using the high-speed disk B-Tree. I've used this technique when storing large amounts (up to 50-MB files) of X/Y curve objects. These file-management classes are robust and extremely fast and provide an alternative storage mechanism for data that does not fit well within the relational paradigm. (For details on an application that implements this feature, see my article "Simplifying C++ GUI Development," DDJ, September 1995.)

In Listing Nine, which illustrates how to use the Tools.h++ object-database management facilities, my address is stored on the disk file with the key "Perry." As Listing Ten shows, retrieving the address is straightforward.

Conclusion

The parts of the Tools.h++ library I've examined in this article barely scratch the surface of what RogueWave offers. The library also provides database wrappers, graphics libraries, linear-algebra support, a Motif wrapper, and even its own version of the Booch Components. In short, RogueWave has a tool for just about any standard programming task.

For More Information

Tools.h++

RogueWave Software

260 SW Madison Avenue

Corvallis, OR 97333

503-754-3010

http://www.roguewave.com

Table 1: Tools.h++ template features.

Feature                                        Benefit

Dynamic resizing                               No extra coding or checks required.
Array indexing for all vector templates        arr[i]...
Searching                                      <i>find</i> and <i>index</i> operators locate members.
Insertion                                      Resizing.
Removal                                        Any items in the collection can be removed.
Entry count                                    <i>entries()</i> method returns number of items.

Listing One

RWCString wonderTool("Rogue");
wonderTool += "Wave tools.h++";
cout << wonderTool << endl;   // Prints "RogueWave tools.h++" 
                              //             ( '+=' concatenates)
RWCString aString = "I am 34.5 years young";
RWCRegexp re1("[0-9]+*[\\.]*[0-9]*");   // Construct regexp object.
                    // Recognizes decimal #s.
aString(re1) = "184.9";
cout << aString << endl; // Prints "I am 184.9 years young"

Listing Two

    
size_t ind = aString.index ( "young" );
aString(ind,0) = "old";
cout << aString << endl; // Prints "I am 184.9 years old"
RWCTokenizer tok ( aString );   // Class RWCTokenizer performs 
                // strtok-like functions.
cout << tok() << endl; // Prints "I"
cout << tok() << endl; // Prints "am"
cout << aString << endl; // Prints "I am 184.9 years old"

Listing Three

RWDate today;
cout << today << endl; // Prints the current date (default constructor)
RWDate weekAgo = today-7; // Subtracts 7 days from today.
RWDate myBirthDay ( 24, 2, 61); // m,d,y constructor.
cout << myBirthDay.weekDayName() << endl; // Prints "Friday"! 
cout << today - myBirthDay << endl;  // Prints number of days I've been alive.

Listing Four

// This example alters the date string for French-speaking users.
RWLocale& french = *new RWLocaleSnapshot("fe");
cout << myBirthday.asString ( 'x', french ) << endl;

Listing Five

struct XYPoint {
    RWDate xDate; // Independent (X) value.
    float yValue; // Dependent (Y) value.
    RWCString ptDescription;    // Text associated w/point.
};

Listing Six

class XYPoint 
{
private:
    
    RWDate xDate;   // Independent (X) value.
    float yValue;   // Dependent (Y) value.
    RWCString ptDescription;// Text associated w/point
public:
    // Main constructor.
    XYPoint ( unsigned mo, unsigned day, 
        unsigned year, float val, const char* descrip ) :
        xDate ( day, mo, year ), yValue(val),
        ptDescription ( descrip ) { }
    // Key-only constructor.
    XYPoint ( unsigned mo, unsigned day, unsigned year ) :
        xDate ( day, mo, year), yValue(0.0), ptDescription()
        { }
    // Default constructor.
    XYPoint() : 
        xDate(), yValue(-9999.99), ptDescription() { }
    // Copy constructor.
    XYPoint ( const XYPoint& xyp ) :
        xDate(xyp.xDate), 
        yValue(xyp.yValue),
        ptDescription(xyp.ptDescription ) { }
    // Assignment operator = 
    XYPoint& operator=(const XYPoint& xyp )
    {
        xDate = xyp.xDate;
        yValue = xyp.yValue;
        ptDescription = xyp.ptDescription;
    }
    // Equality == operator
    RWBoolean operator==( const XYPoint& xyp ) const
    {
        if ( xDate == xyp.xDate )
            return TRUE;
        else
            return FALSE;
    }
    // Less then < operator.
    RWBoolean operator<( const XYPoint& xyp ) const
    {
        if ( xDate < xyp.xDate )
            return TRUE;
        else
            return FALSE;
    }
};

Listing Seven

RWTValSortedVector<XYPoint> myValCurve; // Create a new curve of XYPoints.
// Insert 1/1/95, 2/1/95, 3/1/95, 4/1/95 data points into curve.
myValCurve.insert ( XYPoint ( 1, 1, 95, 1000.0, "Point 1" ) ); 
myValCurve.insert ( XYPoint ( 1, 2, 95, 1050.0, "Point 2" ) );
myValCurve.insert ( XYPoint ( 1, 4, 95, 2000.0, "Point 4" ) );
myValCurve.insert ( XYPoint ( 1, 3, 95, 1500.0, "Point 3" ) );
// Value-based collection now has 4 points in correct order.
XYPoint locator ( 1, 1, 95 );   // Create a temporary point to be 
                // used for searching.
myValCurve.remove ( locator );  // Remove the first point only.
cout << myValCurve.entries() << endl;   // Prints '3', since 3 pts are left.
myValCurve.clear(); // Removes all elements.
RWTPtrSortedVector<XYPoint> myPtrCurve; // Pointer-based analog of above.
myPtrCurve.insert ( new XYPoint ( 1, 1, 95, 1000.0, "Point 1" ) ); 
myPtrCurve.insert ( new XYPoint ( 1, 2, 95, 1050.0, "Point 2" ) );
        ...
        ...
// Pointer-based collection must explicitly free the dynamically
// allocated points!  This can be the source of huge memory leak! BEWARE! ;-)
myPtrCurve.clearAndDestroy();   // Deletes each XYPoint and 
                    // removes from collection.

Listing Eight

RWvostream& operator<< ( RWvostream& s ) const
{
    s << xDate << yValue << ptDescription;
    return s;
}
RWvistream& operator>> ( RWvistream& s )
{
    s >> xDate >> yValue >> ptDescription;
    return s;
}

Listing Nine

RWFileManager fm("mydb.dat");   // Create file manager class on a disk file.
RWBTreeOnDisk bt(fm); // Construct B-Tree for the file manager.
RWCString myAddress ( "11915 Merry Lane" );
RWOffset loc = fm.allocate ( myAddress.binaryStoreSize() );
fm.SeekTo ( loc );
fm << myAddress;
bt.insertKeyAndValue ( "Perry", loc );

Listing Ten

RWOffset foundLoc = bt.findValue ( "Perry" );
fm.SeekTo ( foundLoc );
RWCString foundAddress;
fm >> foundAddress;
cout << "My address is: " << foundAddress << endl;


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.