Rogue Wave banner
Previous fileTop of documentContentsIndexNext file

14.6 A Few Friendly Warnings

Persistence is a useful quality, but requires care in some areas. Here are a few things to look out for when you use persistence on objects.

14.6.1 Always Save an Object by Value before Saving the Identical Object by Pointer

In the case of both isomorphic and polymorphic persistence of objects, you should never stream out an object by pointer before streaming out the identical object by value. Whenever you design a class that contains a value and a pointer to that value, the saveGuts and restoreGuts member functions for that class should always save or restore the value then the pointer.

Here is an example that creates a class that uses isomorphic persistence, then instantiates objects of the class and attempts to persist those objects isomorphically. This example will fail. See the explanation that follows the code.

     class Elmer {
     public:
      /*  ... */
      Elroy  elroy_;
      Elroy* elroyPtr_;  // elroyPtr_ will point to elroy_.  
  };

  RWDEFINE_PERSISTABLE(Elmer)

  void rwSaveGuts(RWFile& file, const Elmer& elmer) {

    // Create a value for elroyPtr_ and stream it:
        file << *elroyPtr_; 

    // If elroyPtr_ == &elroy_, store a reference
    // to elroy_ that points to the created value for
    // elroyPtr_:
        file << elroy_;
  }
  
  void rwRestoreGuts(RWFile& file, Elmer& elmer){
  
    // Create a value for elroyPtr_ in memory
    // and change elroyPtr_ to point to that 
    // value in memory:
        file >> elroyPtr_; 

    // Assign reference to value.
    // If elroyPtr_ == &elroy then the value of 
    // elroy_ will already be created but now
    // elroyPtr_ != &elroy so an
    // RWTOOL_REF exception will be thrown:
        file >> elroy_;
  }

  /*  ... */
  RWFile file("elmer.dat");
  Elmer  elmer;
  Elmer  elmer2;
  elmer.elroyPtr_ = &(elmer.elroy_);
  /*  ... */
  file << elmer;  // Trouble is coming ...
  /*  ... */
  file >> elmer2;  // Trouble has arrived. RWTOOL_REF exception!
  /*  ... */

In the above code, the following statement isomorphically saves elmer to file:

file << elmer;

First, the statement calls the insertion operator:

operator<<(RWFile&, const Elmer&)

Since elmer hasn't been saved yet, the value of elmer will be saved to file and that value will be added to the isomorphic save table. However, elmer has the members elroy_ and elroyPtr_, which must be saved as part of saving elmer. The members of elmer are saved in rwSaveGuts(RWFile&, const Elmer&).

The function rwSaveGuts(RWFile&, const Elmer&) saves elroyPtr_ first, then elroy_. When rwSaveGuts saves the value *elroyPtr_, it calls the insertion operator operator<<(RWFile&, const Elroy&).

The insertion operator operator<<(RWFile&, const Elroy&) sees that elmer.elroyPtr_ hasn't been stored yet, so it saves the value *(elmer.elroyPtr_) to file, and makes a note in the isomorphic save table that this value has been stored. Then operator<<(RWFile&, const Elroy&) returns to rwSaveGuts(RWFile&, const Elmer&).

Back in rwSaveGuts(RWFile&, const Elmer&), it's time for elmer.elroy_ to be saved. In this example, elmer.elroyPtr_ has the same address as elmer.elroy_. Once again the insertion operator operator<<(RWFile&, const Elroy&) is called, but this time the insertion operator notices from the isomorphic save table that *(elmer.elroyPtr_) has already been stored, so a reference to *(elmer.elroyPtr_) is stored to file instead of the value.

Everything seems okay, but trouble is looming. Trouble arrives with the statement:

file >> elmer2;

This statement calls the extraction operator operator>>(RWFile&, const Elmer&). Since elmer2 hasn't been restored yet, the value of elmer2 will be extracted from file and added to the isomorphic restore table. In order to extract the value of elmer2, the members elroy_ and elroyPtr_ must be extracted. The members of elmer2 are extracted in rwRestoreGuts(RWFile&, Elmer&).

The function rwRestoreGuts(RWFile&, Elmer&) restores elroyPtr_ first, then elroy_. When rwRestoreGuts restores the value *elroyPtr_, it calls the extraction operator operator>>(RWFile&, Elroy*&).

The extraction operator operator>>(RWFile&, Elroy*&) sees that elmer2.elroyPtr_ hasn't yet been extracted from file. So the extraction operator extracts the value *(elmer2.elroyPtr_) from file, allocates memory to create this value, and updates elmer2.elroyPtr_ to point to this value. Then the extraction operator makes a note in the isomorphic restore table that this value has been created. After making the note, operator>>(RWFile&, Elroy&) returns to rwRestoreGuts(RWFile&, Elmer&).

Back in rwRestoreGuts(RWFile&, Elmer&), it's time to restore elmer2.elroy_. Remember that elmer.elroyPtr_ has the same address as elmer.elroy_. The rwRestoreGuts function calls the extraction operator operator>>(RWFile&, Elroy&), which notices from the isomorphic save table that *(elmer2.elroyPtr_) has already been extracted from file. Because a value has already been stored in the restore table, the extraction operator extracts a reference to *(elmer.elroyPtr_) from file instead of extracting the value. But the extraction operator notices that the address of the value that elmer2.elroyPtr_ put into the restore table is different from the address of elmer2.elroy_. So operator>>(RWFile&, Elroy&) throws an RWTOOL_REF exception, and the restoration is aborted.

The solution to the problem in this particular case is easy: reverse the order of saving and restoring Elmer's members! Here is the problem:

// WRONG!

void rwSaveGuts(RWFile& file, const Elmer& elmer) {
  file << *elroyPtr_; 
  file << elroy_;
}

void rwRestoreGuts(RWFile& file, Elmer& elmer){
  file >> elroyPtr_; 
  file >> elroy_;
}

Instead, you should write your functions the following way:

// RIGHT!

void rwSaveGuts(RWFile& file, const Elmer& elmer) {
  file << elroy_;
  file << *elroyPtr_; 
}

void rwRestoreGuts(RWFile& file, Elmer& elmer){
  file >> elroy_;
  file >> elroyPtr_; 
}

If you correct rwRestoreGuts and rwSaveGuts as suggested above, then the isomorphic save and restore tables can use the address of Elmer::elroy_ to update Elmer::elroyPtr_ if necessary.

Summary: Because of the possibility of having both a value and a pointer that points to that value in the same class, it's a good idea to define your rwSaveGuts and rwRestoreGuts member functions so that they always operate on values before pointers.

14.6.2 Don't Save Distinct Objects with the Same Address

You must be careful not to isomorphically save distinct objects that may have the same address. The internal tables that are used in isomorphic and polymorphic persistence use the address of an object to determine whether or not an object has already been saved.

The following example assumes that all the work has been done to make Godzilla and Mothra isomorphically persistable:

class Mothra {/*  ... */};
RWDEFINE_PERSISTABLE(Mothra)

struct Godzilla {
  Mothra  mothra_;
  int     wins_;
};

RWDEFINE_PERSISTABLE(Godzilla)
/* ... */
Godzilla  godzilla;
/*  ... */
stream << godzilla;
/*  ... */
stream >> godzilla;       // The restore may be garbled!

When godzilla is saved, the address of godzilla will be saved in an isomorphic save table. The next item to be saved is godzilla.mothra_. Its address is saved in the same internal save table.

The problem is that on some compilers godzilla and godzilla.mothra_ have the same address! Upon restoration of godzilla, godzilla.mothra_ is streamed out as a value, and godzilla is streamed out as a reference to godzilla.mothra_. If godzilla and godzilla.mothra have the same address, the restore of godzilla fails because the extraction operator attempts to initialize godzilla with the contents of godzilla.mothra_.

There are two ways to overcome this difficulty. The first is to structure your class so that simple data members, such as int, precede data members that are isomorphically persistent. Using this method, class Godzilla looks like this:

struct Godzilla {
  int    wins_;
  Mothra mothra_;   // mothra_ now has a different address.
};

If Godzilla is structured as shown here, mothra_ is displaced from the front of godzilla and can't be confused with godzilla. The variable wins_, of type int, is saved with simple persistence and is not stored in the isomorphic save table.

The second approach to solving the problem of identical addresses between a class and its members is to insert an isomorphically persistable member as a pointer rather than a value. For Godzilla this would look like:

struct Godzilla {
  Mothra* mothraPtr_;// mothraPtr_ points to a different address.
  int     wins_;
};

In this second approach, mothraPtr_ points to a different address than godzilla, so confusion is once again avoided.

14.6.3 Don't Use Sorted RWCollections to Store Heterogeneous RWCollectables

When you have more than one different type of RWCollectable stored in an RWCollection, you can't use a sorted RWCollection. For example, this means that if you plan to store RWCollectableStrings and RWCollectableDates in the same RWCollection, you can't store them in a sorted RWCollection such as RWBTree. The sorted RWCollections are RWBinaryTree, RWBTree, RWBTreeDictionary, and RWSortedVector.

The reason for this restriction is that the comparison functions for sorted RWCollections expect that the objects to be compared will have the same type.

14.6.4 Define All RWCollectables That Will Be Restored

Make certain that your program declares variables of all possible RWCollectable objects that you might restore. For an example of this practice, see Section 14.5.3.2, above.

These declarations are of particular concern when you save an RWCollectable in a collection, then attempt to take advantage of polymorphic persistence by restoring the collection in a different program, without using the RWCollectable that you saved. If you don't declare the appropriate variables, during the restore attempt the RWFactory will throw an RW_NOCREATE exception for some RWCollectable class ID that you know exists. The RWFactory won't throw an RW_NOCREATE exception when you declare variables of all the RWCollectables that could be polymorphically restored.

The problem occurs because your compiler's linker only links the code that RWFactory needs to create the missing RWCollectable when that RWCollectable is specifically mentioned in your code. Declaring the missing RWCollectables gives the linker the information it needs to link the appropriate code needed by RWFactory.



Previous fileTop of documentContentsIndexNext file
©Copyright 1999, Rogue Wave Software, Inc.
Send mail to report errors or comment on the documentation.