The primary function of a database application is to store and retrieve data. If you're implementing a relatively simple data model, your database application may be able to store this data in records it identifies by looking at one particular part of each record. As it turns out, many applications that aren't primarily database applications could benefit from having some database or lookup table functionality like this built in.
Unfortunately, writing a complete database system inside an application isn't a practical consideration for most programmers. This is particularly true when the application's data is simple and the search criteria are straightforward.
In this article, we'll show you how to use the Association
and Dictionary classes from the Borland Container class
library to create a simple key-value lookup table. If you're
not familiar with the Container class library, see Container class background for more information.
In the Container class library, the Dictionary class defines an unordered set of unique (no duplicates) Association objects. The Association class derives from the Object class, and in turn holds two Object-class-derived items: a key object and a value object.
To use a Dictionary object, you'll first create a group of Association objects from key-value object pairs. Then, you'll add each Association object to the Dictionary object by using its add() member function.
To retrieve any of the value objects (embedded in the Association objects and therefore contained in the Dictionary object), you simply call the Dictionary object's lookup() member function with a key object that matches one of the key objects in the Dictionary object. Figure A shows the relationship between a Dictionary object, the Association objects it holds, and the Association objects' key and value objects.
Figure A - Dictionary class objects hold Association class objects that associate key and value objects.
For example, suppose you want to create a simple lookup table
in an application that displays the first line of a song when
you supply the song title. You can create each entry using the
following format:
String title("Cherish"); String firstLine("Cherish is the word I use..."); Association theAssociation(title, firstLine);
Then, you can add that Association object to the dictionary
by using the lines
Dictionary songDictionary; songDictionary.add(theAssociation);
To display the first line of the song "Cherish" in
songDictionary, you then write
Association& song = songDictionary.lookup(String("Cherish")); cout << song.value() << endl;
Now let's work through a more complete example that demonstrates
how you can use Association objects to bind key
objects to value objects at runtime. Then, we'll
add the objects to a Dictionary object and search for
value objects using matching key objects.
To begin, launch the Borland C++ version 3.1 DOS Integrated Development Environment (IDE). When the IDE's menu bar and desktop appear, choose New from the File menu. When the edit window for the new file appears, enter the code from Listing A.
Listing A: CMDLOOK.CPP
#include <string.h> #include <strng.h> #include <assoc.h> #include <dict.h> #include <iostream.h> int main() { char keyBuffer[10]; cout << "Press a key for the quit command"; cout << endl; cin >> keyBuffer; String QuitCommand("Quit Key"); String QuitKey(keyBuffer); Association Quit(QuitKey, QuitCommand); Quit.ownsElements(0); cout << "Press a key for command #1" << endl; cin >> keyBuffer; String Cmd1Command("Command #1"); String Cmd1Key(keyBuffer); Association Cmd1(Cmd1Key, Cmd1Command); Cmd1.ownsElements(0); cout << "Press a key for command #2" << endl; cin >> keyBuffer; String* Cmd2Command = new String("Command #2"); String* Cmd2Key = new String(keyBuffer); Association Cmd2(*Cmd2Key, *Cmd2Command); cout << "Press a key for command #3" << endl; cin >> keyBuffer; String* Cmd3Command = new String("Command #3"); String* Cmd3Key = new String(keyBuffer); Association Cmd3(*Cmd3Key, *Cmd3Command); Dictionary CmdDictionary; CmdDictionary.ownsElements(0); CmdDictionary.add(Quit); CmdDictionary.add(Cmd1); CmdDictionary.add(Cmd2); CmdDictionary.add(Cmd3); String LastKey; do { cout << "Enter a command - "; cin >> keyBuffer; LastKey = String(keyBuffer); Association& command = CmdDictionary.lookup(LastKey); if(command != NOOBJECT) { cout << command.value() << endl; } else cout << "Invalid command!" << endl; } while(!(LastKey.isEqual(QuitKey))); return 0; }
In this program, we create four Association objects: Quit, Cmd1, Cmd2, and Cmd3. Each Association object holds a String object with the single ASCII character value of the corresponding keyboard key, and another String object that contains a text description of the command. (An Association object can hold String objects because the String class derives from the Object class.)
The constructor for the Association class takes a reference to a key object (the first parameter) and a reference to a value object (the second parameter). As you'll see later, it's important to know this because you'll pass the key and value identifiers only if you create them on the stack (we create Quit and Cmd1 this way). If you create the key and value objects using heap memory (via the new operator), you'll need to dezzzzreference their identifiers in the Association object's constructor (we do this for Cmd2 and Cmd3).
To load the CmdDictionary object, we simply call the add() member function using each of the four Association objects we created earlier. Then, we enter a do-while loop to gather input from the keyboard and put the ASCII value of the key into the LastKey object.
In this loop, we first check to see if the CmdDictionary
object contains an Association object for the current
command key. If an appropriate object doesn't exist, we
display an error message. If a matching object does exist, we
display the String object that corresponds to the current
command key. The loop will repeat until you press the key that
initiates the quit command.
When you finish entering the code, choose Save from the File menu. When the Save As Filename dialog box appears, enter CMDLOOK.CPP in the Save File As entry field and click OK.
Next, choose Application... from the Options menu. In the Set Application Options dialog box, click DOS Standard and then click OK.
To build and run CMDLOOK.EXE, choose Run from the Compile menu. After the compiler finishes building the executable file, the IDE will open a DOS shell and run the application.
When the application starts, it first prompts you to press the keyboard key that will issue the quit command. Enter q as the quit command key and press [Enter].
Next, the application will use a series of similar prompts to ask you for the keyboard keys that correspond to three commands. Enter 1, 2, and 3 as the three command keys.
After you enter the third command key, the application will run
a loop that asks you to enter a command key and then displays
the name of the corresponding command the application finds in
the CmdDictionary object. If you enter a key that isn't
one of the three command keys or the quit key, you'll see
the following message:
Invalid Command!
Now, press one of the three command keys and then press [Enter]. As you enter various commands, the output from this application should resemble the data shown in Figure B.
Figure B - As you enter various commands, CMDLOOK.EXE displays matching command descriptions from the CmdDictionary object.
Press a key for the quit command q Press a key for command #1 1 Press a key for command #2 2 Press a key for command #3 3 Enter a command - 1 Command #1 Enter a command - 3 Command #3 Enter a command - 2 Command #2 Enter a command -
When you've tried all the commands, enter q to quit
CMDLOOK.EXE and return to the IDE. To quit the IDE, choose Quit
from the File menu.
By default, Dictionary and Association objects own the objects they hold. This means they assume responsibility for deleting the objects they own unless you tell them otherwise.
This is particularly important in a case like the one we've shown. Because we create some of the String objects and all the Association objects in stack memory (these objects are all local to the main() function), the compiler will call the destructor for each object at the return point of the enclosing function.
Unfortunately, if a Dictionary object owns an Association object that resides on the stack, the Dictionary object will try to delete that Association too. Similarly, if an Association object owns the objects it holds, it will also try to delete those objects. (Calling delete for the same object more than once will usually crash a DOS program or cause a General Protection Fault in a Windows program.)
To avoid this problem, you can tell a Dictionary or Association object that it doesn't own the objects it contains by calling the ownsElements() member function of either class with a value of 0 (zero). This member functionwhich these classes inherit from the TShouldDelete classallows you to specify the ownership status of a Dictionary or Association object with regard to the items it holds.
By telling the Dictionary or Association object
that it doesn't own its elements, you're taking
responsibility for deleting these objects at the appropriate time.
In our example, this is easy because we create and delete the
CmdDictionary object at the same time we create and destroy
the command Association objects and some of the String
objects. If you're creating anything other than a trivial
application like this, you'll probably want to create your
Association and key-value objects on the heap.
Many applications need simple lookup table functionality. By learning
to use the Dictionary and Association classes
from Borland's Container class library, you can add this
capability without writing the code from scratch.
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.