Currently C++ is the language of choice for developing the client side of client/server applications even though it has little in common with SQL, its server-side counterpart. In particular, C++ does not support persistence, the ability of data objects to outlive the programs that created them. Fortunately, it's possible to give the language that capability through add-on solutions. Last month we looked at one such solution: Subtle Software's Subtleware, which manages C++ object storage (persistence) in relational databases (see "Subtleware: C++ persistence," March 1995).
Although the term client/server and the topics for this column often assume large systems and their attendant databases, there are many instances in which developers need to prepare standalone applications to solve some immediate problem or to even model a client/server system. For example, you may use C++ instead of some other object-oriented language for any number of reasons: lack of tools or expertise, leveraging existing code, efficiency, and so on. With C++, though, to be truly quick and easy, you need to be able to wave some magic wand over your data and have it saved for later reuse. And you don't want that magic wand to take the form of a large, complex, full-blown database system: Enter Poet (Persistent Objects and Extended-database Technology) from Poet Software Corp. (Santa Clara, CA) and Raima Object Manager (ROM) from Raima (Issaquah, WA). They both provide C++ object persistence without the muss and fuss of a full-blown, large systems database.
Easy as 1-2-3?
An ideal solution for C++ object persistence is a special persistence class that lets you inherit the class into your class definitions and thereby make instances of that class persistent (or potentially persistent). It should also give you a set of functions for opening named databases for persistent object storage and retrieval and for doing other interesting things with databases (creating, deleting, and so on). Through operator overloading, macros, and other syntactic chicanery, the C++ persistence class should make object storage transparent to your programs, so you don't have to call a function to save your object when it changes.
In other words, a persistence class should be the magic wand you wave over your data to put in permanent storage. Most importantly, it would include a "constructor" function you call when you want to create a new object that persists. It would create space for that object in permanent storage and return a pointer that looks like any other C++ pointer.
Unfortunately, persistent objects open a Pandora's box of issues that make the task less straightforward than it may seem. For example, once you have created persistent objects, how do you get them back the next time your program runs? Do you want all of them, or just certain ones? Which certain ones?
Names and spaces
Some conventional programming languages, like Lisp and APL, support persistence. They manage it rather simply; they have workspaces that contain function and variable definitions and are stored in files. When you invoke the language interpreter with a filename (or give it a load filename command), it loads the contents of that workspace, making all of its functions and variables available to you by name. The names of things in a workspace are closely associated with the things themselves and are saved along with them.
In C++, on the other hand, objects are not as tightly bound with their names. For example, if an object is called "fred" in a C++ program, the name fred is not part of the object itself but is simply a name the program uses as its own handle to the object, and the next time the program runs, it can't retrieve the object by commanding some underlying storage manager to "get me fred." This lack of implicit name association makes it hard for C++ code to identify a particular object without a layer of extra help.
Another way of accessing objects is through sets, such as all objects of a given type (the type's extent) or subsets thereof -- a paradigm well known to relational-database programmers. Because C++ supports user-defined object types, it's desirable to access C++ objects in this way, as opposed to Lisp or APL. Overall, persistent C++ solutions have to provide their own ways of letting programmers find previously saved objects, whether individually or in sets. The language doesn't provide this capability by itself.
Complicating things even further is the need to track relationships among complex objects. For example, if you have a persistent object whose attributes (data members) include pointers to several other objects, what happens if those objects aren't persistent? When you retrieve the main object, you automatically should retrieve all of the others, as well. Or should you? Answers to such questions aren't obvious; they imply a need for fairly sophisticated memory-management strategies.
Poet and ROM
If you add up all the features a C++ persistence solution must support, the tool begins to look suspiciously like a database-management system. That's why the makers of both Poet and Raima Object Manager call their products databases, rather than persistence add-ons. While both products fall short of providing the magic wand of C++ persistence classes, they both provide solid support for persistent C++ objects on the desktop, as well as some fairly sophisticated database features.
Despite Poet's database features, it looks like a product that was intended to support persistence in C++ from its inception (in 1992). Poet 2.1 is available in single- and multiuser configurations; the single-user Personal Edition runs on Microsoft Windows, Macintosh, OS/2, NextStep, and various flavors of Unix. On the Windows platform, it supports C++ compilers from Microsoft, Borland, and Symantec.
Poet makes you declare persistent classes with the special persistent keyword rather than letting your C++ objects inherit a persistence class. The system includes a C++ precompiler that looks for persistent class definitions, decides how to implement their storage, and generates appropriate C++ code. Poet does not implement persistence transparently: Like Subtle Software's Subtleware, it makes you call a function (store()) whenever you want to write object changes to disk.
Poet retrieves existing objects from a database using the power of C++. For each persistent class, the precompiler generates a class called classnameAllSet. After you call the connect() and open() functions to open a database, you can retrieve a class' extent by simply creating an AllSet for the class. Then you use get(), a built-in function that works on AllSets, to retrieve individual objects from the set. Poet also generates a class called classnameQuery, which lets you set up queries on sets according to selection criteria and invoke them through the set's query() function.
Raima Object Manager (ROM) takes a different approach to C++ object persistence. ROM comes from a line of small-scale "embedded databases" -- compact, fast database engines meant to be used within standalone applications. The company's original product was db_Vista, a proprietary database for PCs that used the network data model. Its latest incarnation is called Raima Data Manager (RDM), which runs on a variety of platforms, including MS-DOS, Windows, OS/2, and various flavors of Unix, and includes relational, sequential, and network data model support.
ROM 3.0 is an add-on module that can be used with either RDM or Velocis, Raima's new client/server database system. (The RDM distribution includes ROM.) RDM's network data model makes it ideal for supporting persistent C++ objects, since the network model is somewhat like an object-oriented data model without the methods. And RDM is a mature, stable product with a large installed base.
If you want to create databases from scratch with ROM, its dependency on RDM makes it necessary to create database definitions in separate files using Raima's Data Definition Language (DDL), which looks like, but isn't really, the C language. ROM's code generator then creates C++ class definitions for your database that use C++ syntactic features -- cleverly, if unintuitively -- for doing data querying and navigation.
ROM supports a technique for adding persistence to existing C++ applications that closely resembles the magic wand. It centers on a persistence class called StoreObj. To use it, you include Raima's header files in your source code. Then you can make an existing class persistent by inheriting StoreObj into your class definitions and making a few other, relatively minor changes. Once you have done this, you can use member functions of StoreObj to store and retrieve instances of your class to and from databases. Like Poet, ROM's storage management is not transparent.
If you want to convert an existing C++ application so it handles persistent data, Poet and ROM can help. They aren't magic wands, but they both contain interesting features that make them worthwhile additions to your C++ tool chest.
About the author
Contributing editor Bill Rosenblatt is director of Publishing Systems in the New York City office of the Times Mirror Co. Reach him at firstname.lastname@example.org.
Bill Rosenblatt is the author of Learning the Korn Shell and a coauthor of Learning Gnu Emacs and Learning the Bash Shell. You can buy these at Amazon.com Books. Select the hyperlinks to learn more about each and Amazon.com.
A list of Bill Rosenblatt's Client/Server columns in SunWorld Online.
If you have problems with this magazine, contact email@example.com
Last updated: 1 April 1995