12.3 RWStringID
Many Rogue Wave clients have asked for a larger range of possible class identifiers for
RWCollectable classes than is available using
RWClassID. We did not change the meaning of
RWClassID, in order to preserve backward compatibility for existing polymorphically persisted files, but we did add a new kind of class identifier,
RWStringID.
An
RWStringID is an identifier for
RWCollectables. It is derived from
RWCString, and may be manipulated by any of the
const RWCString methods. The non-
const methods have been hidden to prevent the disaster that could occur if the
RWStringID of a class changed at run time.
You can associate an
RWStringID with an
RWCollectable class in one of two ways: pick the
RWStringID for the class, or allow the library to automatically generate an
RWStringID that is the same sequence of characters as the name of the class; for example,
class MyColl : public RWCollectable would get the automatic
RWStringID “
MyColl”.
You specify a class with a fixed
RWClassID and generated
RWStringID by using the macro
RW_DEFINE_COLLECTABLE_CLASS_BY_ID as follows:
RW_DEFINE_COLLECTABLE_CLASS_BY_ID(USER_MODULE,ClassName, ClassID)
RW_DEFINE_COLLECTABLE_CLASS_BY_ID(USER_MODULE,MyCollectable1,0x1000) // for example
You specify a class with a fixed
RWStringID and a generated
RWClassID by using the new macro
RW_DEFINE_COLLECTABLE_CLASS_BY_NAME as follows:
RW_DEFINE_COLLECTABLE_CLASS_BY_NAME(USER_MODULE, ClassName, StringID)
RW_DEFINE_COLLECTABLE_CLASS_BY_NAME(USER_MODULE, MyCollectable2, "Second Collectable") // for example
Using the examples above, you could write:
// First set up the experiment
MyCollectable1 one; MyCollectable2 two;
// All running RWClassIDs are guaranteed distinct
one.isA() != two.isA();
// Every RWCollectable has an RWStringID
one.stringID() == "MyCollectable1";
// There are several ways to find ids
RWCollectable::stringID(0x1000) == "MyCollectable1";
two.isA() == RWCollectable::classID("Second Collectable");
12.3.1 Duration of Identifiers
Providing polymorphic persistence between different executions of the same or different programs requires a permanent identifier for each class being persisted. Until now, the permanent identifier for any
RWCollectable has been its
RWClassID. For each class that derives from
RWCollectable, the macro
RW_DEFINE_COLLECTABLE_CLASS_BY_ID caused code to be generated that forever associated the class and its
RWClassID. This identification has been retained, but in the current version of the Essential Tools Module you may choose the
RW_DEFINE_COLLECTABLE_CLASS_BY_NAME macro, which will permanently link the chosen
RWStringID with the class.
The addition of
RWStringID identifiers will result in more identifiers, and more self-documenting
RWCollectable identifiers, than were possible under the old restriction. To accommodate the new identifiers, a temporary
RWClassID is now generated for each
RWCollectable class that has an
RWStringID specified by the developer. These
RWClassIDs are built as needed during the run of an executable, and remain constant throughout that run. However, they may be generated in a different order on a different executable or during a different run, so they are not suitable for permanent storage.
12.3.2 Programming with RWStringIDs
RWCollectable now has a new regular member function, and two new static member functions. In order to maintain link compatibility with objects compiled against previous versions of the Essential Tools Module, none of these functions is virtual. The functions are therefore slightly less efficient than they would be if we broke link-compatibility.
The new regular member function is:
RWStringID stringID() const;
The new static member functions are:
RWStringID stringID(RWClassID); // looks up RWStringID
RWClassID classID(RWStringID); // looks up classID
RWFactory also includes the following new functions:
void addFunction(RWuserCreator, RWClassID, RWStringID);
RWCollectable* create(RWStringID) const;
RWuserCreator getFunction(RWStringID) const;
void removeFunction(RWStringID);
RWStringID stringID(RWClassID) const;
RWClassID classID(RWStringID) const;
You can use
RWCollectables that ship with the Essential Tools Module and
RWCollectables that have been defined with fixed
RWClassIDs exactly as in previous versions of the Essential Tools Module. For instance, you could use this common programming idiom:
RWCollectable *ctp; // assign the pointer
if (ctp->isA() == SOME_CONST_CLASSID) // do a specific thing
However, when you use
RWCollectables that have user-provided
RWStringIDs, which implies any non-permanent
ClassIDs, you must anticipate that the
RWClassID may have different values during different runs of the executable. For these classes, there are two possible idioms to replace the one above:
RWCollectable *ctp;
// assign the pointer somehow
// use with existing RWCollectable for comparison:
// comparison will be faster than comparing RWStringIDs
if(ctp->isA() == someRWCollectablePtr->isA())
// you may code to that class interface
// ...
// idiom to hard code the identification. Slightly
// slower because string comparisons are slower than int
// comparisons; also stringID() uses a dictionary lookup.
if (ctp->stringID() == "Some ID String") {
// you may code to that class interface
}
12.3.3 Implementation Details of RWStringID
The next few sections cover implementation details of
RWStringID. If you are curious about how we manage to provide virtual functionality without adding virtual methods, or if you are interested in issues of design, efficiency, and other specifics, these sections are for you.
12.3.3.1 Automatic RWClassIDs
Automatic RWClassIDs are created in a systematic way from unused RWClassIDs in the range 0x9200 to 0xDAFF. There are 18,687 possible such RWClassIDs, so only extraordinary programs can possibly run out. However, we are used to dealing with extraordinary customers, so we feel we must warn you: you will not be able to build and use more than 18,687 different classes with automatically generated RWClassIDs in any one program. 16-bit DLLS will also accumulate automatic RWClassIDs while they are loaded in memory
Note that this implies nothing about the total number of objects of each class that you may have. That number is limited only by the requirements of your operating system and compiler. Of course, you also have access to the full set of
RWClassIDs below 0x8000—that is, 32,767 more possible
RWCollectable s—but they will not be automatically generated. You must specify them manually.
12.3.3.2 Implementing Virtuals Via Statics
Since the virtual method
isA() returns a “runtime unique”
RWClassID, we can use this one virtual method to provide an index into a lookup table where various data or function pointers are stored. (This may remind you of C++ built-in
vtables!) Since
RWCollectables already depend on the existence of a single
RWFactory, we chose to use that
RWFactory instance to hold the lookup information.
The static method:
RWStringID RWCollectable::stringID(RWClassID id);
will attempt to look up
id in the
RWFactory instance. If it succeeds in finding an associated
RWStringID, it will return it. Otherwise, it will return
RWStringID("NoID").
The static method:
RWClassID RWCollectable::classID(RWStringID sid)
works in an analogous manner, looking in the
RWFactory instance to see if there is an
RWClassID associated with
sid. If the method finds one, it returns it; otherwise, it returns
RWClassID __RWUNKNOWN.
12.3.3.3 Polymorphic Persistence
Polymorphic persistence of
RWCollectables is not affected by the addition of the new class
RWStringID. Existing files can still be read using newly compiled and linked executables, as long as the old
RWClassIDs are unchanged. New classes that have
RWStringIDs may be freely intermixed with old classes. The storage size of collectables that do not have permanent
RWClassIDs will reflect their larger space requirements, but the storage size of other
RWCollectables will be unaffected.
Note that collections containing
RWCollectables with the same
RWStringID have that
RWStringID stored into a stream or file only once, just as multiple references to the same
RWCollectable are only stored the first time they are seen.
12.3.3.4 Efficiency
Since
RWClassID is more efficient in both time and space than
RWStringID, you may wish to continue using it wherever possible.
RWStringIDs are useful:
For organizations that need to generate unique identifiers for many programming groups;
For third party libraries that need to avoid clashes with other libraries or users;
Anywhere the self-documenting feature of
RWStringID adds enough value to compensate for its slight inefficiencies.
RWStringIDs are generated for all
RWCollectable classes that are compiled under the current version of the Essential Tools Module. This additional code generation has only minor impact on programs that do not use the
RWStringIDs. The
RWFactory will be larger, to hold lookups from
RWClassID and
RWStringID; and startup time will be very slightly longer, to accommodate the addition of the extra data to the
RWFactory.
12.3.3.5 Identification Collisions
While
RWStringID can help alleviate identification collisions, the possibility of collisions between
RWStringIDs of different classes still exists. Collisions can occur:
When an automatically generated
RWStringID conflicts with a user-chosen one;
When one or more classes are accidentally assigned the same
RWStringID;
When two classes in different namespaces have the same name and thus the same automatically generated
RWStringID. This assumes your compiler supports namespaces.
In some cases, collisions like these will be unimportant. Automatically generated RWClassIDs are guaranteed to be distinct from one another and from any legal user-provided RWClassID. The virtual isA() method, the stringID() method, and constructor lookup based on the RWClassID will all continue to work correctly.
There will be some situations, however, where collisions will cause difficulty. Polymorphic persistence of classes with user-chosen
RWStringIDs that collide will not work correctly. In these cases, the data will not be recoverable, even though it is stored correctly. Similarly, user code that depends on distinguishing between classes based only on their
RWStringIDs will fail.
As a developer, you can work to avoid such collisions. First of all, you should use an
RWStringID which is unlikely to collide with any other. For instance, you might choose
RWStringIDs that mimic the inheritance hierarchy of your class, or that imbed your name, your company's name, a creation time, or a file path such as found in revision control systems. And of course, you should always test your program to insure that the class actually associated with your
RWStringID is the one you expected.