September, 1994 - Vol. 1 No. 9
In the article Borland C++ 3.1 Class libraries - Retrieving data with dictionaries and associations from last month's issue of Borland C++ Devel-oper's Journal, we showed you how to implement simple database-style lookup tables by using the Dictionary and Association classes that ship with the 3.1 version of Borland C++. Unfortunately, these classes are part of Borland's older, Object-based container class library, which means Association and Dictionary objects can contain only objects from classes that derive from the Object class.
With the release of C++ 4.0, Borland redesigned the template-based
collection class library (also known as the Borland International
Data Structures, or BIDS, library) to include replacements for
the Dictionary and Association classes. However,
there are some important differences between the old versions
of these classes and the new ones. In this article, we'll
show how to use the new TDictionaryAsHashTable class
and the corresponding association classes.
We've already mentioned the biggest change that Borland
made in the classes that implement dictionaries and associations:
moving them to the BIDS library. While this change allows you
to create associations that use key/value pairs from classes that
don't derive from the Object class, it also forces
you to make some design decisions that the previous version of
the library made for you. First, let's look at the new
classes you'll use to create associations; then, we'll
examine the new dictionary classes.
There are four new association class templates: TDDAssociation, TDIAssociation, TIDAssociation, and TIIAssociation. Each of these classes performs the same functionbinding a key object to a value object.
However, the second and third letters of the template names indicate whether each template's key and value elements are actual objects (D for Direct) or pointers (I for Indirect). The second letter specifies the type of key object you'll use, and the third letter specifies the type of value object.
For example, a TDIAssociation-derived association object
will contain an object for its key (D), but it will contain
a pointer to an object for its value (I). Likewise, a
TIDAssociation-derived association object will have a
pointer to an object for its key and an object for its value.
Obviously, the TDDAssociation and TIIAssociation
classes represent associations that use objects or pointers, respectively,
for both the key and value objects.
To use the new association class templates, you'll use one of the new dictionary class templates. As with the associations, there are different versions of the dictionary classes for working with association objects directly or via a pointer.
There are four dictionary class templates:
TMDictionaryAsHashTable TDictionaryAsHashTable TMIDictionaryAsHashTable TIDictionaryAsHashTable
You'll use the TDictionaryAsHashTable class most often, so we'll focus on this class.
From the standpoint of the class's interface, the most obvious difference between the older Dictionary class and the new TDictionaryAsHashTable class template is the Find() member function. You'll use the TDictionaryAsHashTable class's Find() member function to perform the actual lookup in the dictionary.
At first, the Find() function may seem to duplicate the action of the Dictionary class's lookup() member function. However, the lookup() function returns the found value object directly, while the Find() function returns a pointer to the association that contains the found value.
As you've probably noticed, Borland implemented all the new dictionary class templates as hash tables, just as the company implemented the older Dictionary class. One reason the Association class could contain only objects derived from the Object class was that the Dictionary class had to be able to calculate a hash value for each association it contained. To provide an interface to the Dictionary class for retrieving these values, the Object class declared a pure virtual function hashValue().
The new dictionary class templates call a global function HashValue()
instead. If you create a member function for your key classes
in the form
unsigned HashValue() const;
the dictionary classes will call this member function via a global template function. Instead, you can create your own global function HashValue() to calculate the correct hash value for your objects.
To see how the new dictionary and association class templates
work, let's rewrite the application from last month's
article. To make the differences between the new and old versions
of the classes more obvious, we'll change as little of
the code as possible.
To begin, launch the Borland C++ 4.0 Integrated Development Environment (IDE). When the IDE's main window appears, choose New from the File menu and enter the code from Listing A in the data-file window.
Listing A: CMDLOOK2.CPP
#include <cstring.h> #include "classlib\assoc.h" #include "classlib\dict.h" #include <iostream.h> unsigned HashValue(const string& str) { return str.hash(); } // Calculates the hash // value for string items typedef TDDAssociation<string,string> StringAssociation; int main() { char key[10]; cout << "Press a key for the quit command"; cout << endl; cin >> key; string QuitCommand("Quit Key"); string QuitKey(key); StringAssociation Quit(QuitKey, QuitCommand); cout << "Press a key for command #1" << endl; cin >> key; string Cmd1Command("Command #1"); string Cmd1Key(key); StringAssociation Cmd1(Cmd1Key, Cmd1Command); cout << "Press a key for command #2" << endl; cin >> key; string Cmd2Command("Command #2"); string Cmd2Key(key); StringAssociation Cmd2(Cmd2Key, Cmd2Command); cout << "Press a key for command #3" << endl; cin >> key; string Cmd3Command("Command #3"); string Cmd3Key(key); StringAssociation Cmd3(Cmd3Key, Cmd3Command); TDictionaryAsHashTable<StringAssociation> CmdDictionary; CmdDictionary.Add(Quit); CmdDictionary.Add(Cmd1); CmdDictionary.Add(Cmd2); CmdDictionary.Add(Cmd3); string LastKey; do { cout << "Enter a command - "; cin >> key; LastKey = string(key); StringAssociation temp(LastKey, ""); StringAssociation* command = CmdDictionary.Find(temp); if(command != 0) { cout << command->Value() << endl; } else cout << "Invalid command!" << endl; } while(LastKey != QuitKey); return 0; }
When you finish entering the code, choose Save from the File menu. In the Save File As dialog box, enter CMDLOOK2.CPP as the filename and click OK. Before you compile and run this program, let's look at a few details in the code.
First, notice the global HashValue() function we create. Since we use objects from the string class for our keys, we can write this function to simply call the hash() member function (note the different name) of the corresponding string object.
Next, we declare the StringAssociation type using the TDDAssociation class template with the string class for both the key and value parameters. Using a StringAssociation object is somewhat equivalent to using the Association class with String objects in the previous version of the program.
To create the dictionary, we first declare the variable CmdDictionary, using the class template TDictionaryAsHashTable and the StringAssociation type as the association parameter. Unfortunately, the design of the new association class templates prevents you from using associations that contain both direct (objects) and indirect (pointers) key/value elements, because you have to specify the type of association when you create the dictionary. So, we changed the construction of the Command #2 and #3 key and value objects to make them direct (nonpointer) objects.
If you're not familiar with the syntax that we used to create the dictionary, it basically creates a new object without a formal name for the type. Normally, you'll want to name types you create from a template class by using a typedef statement as we did last month. However, we need to create only one dictionary object, so we used this shortcut.
Finally, we change the lookup loop to work correctly with the
new Find() member function and to take advantage of some
of the string class's behavior. For example, to
create the temp variable, we used the string
class's const char* conversion constructor to
create a temporary object from the literal "".
Then, we changed the while() statement to use the string
class's overloaded inequality operator (!=).
Right-click on the window for the file CMDLOOK2.CPP and choose Target Expert from the pop-up menu. When the TargetExpert dialog box appears, choose EasyWin from the Target Type list box and select the Class Library check box. Click OK when you're finished.
Now, choose Run from the Debug menu to build and run the program. When the program runs, you'll enter the keys for the four commands and then enter the command loop.
To quit the CMDLOOK2 program, enter the key you assigned to the
Quit command. To close the EasyWin window, double-click on its
System menu icon.
It's important that you make the parameter of the HashValue()
function const. If you don't, the compiler will
try to find a better match by using the class library's
template function
template <class T> inline unsigned HashValue(const T& t) { return t.HashValue(); }
and will display an error when it can't find a HashValue() member function in the string class.
This is because the TDictionaryAsHashTable class calls
the HashValue() function when it's searching for
a matching key. To keep the function from changing the key object,
the TDictionaryAsHashTable class converts the key to
a const object before calling the function HashValue().
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.