ProxyDict Programmer's Guide Ed Frank University of Pennsylvania ===== DRAFT ===== WARNING: This is more a full writeup than a README file. I'll make a doc directory soon, but for now I'm replacing the old, useless README file with this. This is probably not going to be useful for a typical BaBar programmer. It is more useful for someone building a new ProxyDict or new Front End. A separate writeup on the BaBar Ifd front-end is needed Last modified: 26 Feb 2000, Ed Frank Contacts: Ed Frank, Univ. of Pennsylvania, efrank@upenn5.hep.upenn.edu Introduction The ProxyDict package provides a dictionary, but one with several novel features. The first novel feature that ProxyDict provides, is the ability to store data of several different C++ types. Although the dictionary stores more than one kind of data, it provides the user with these data in a type-safe way. This means that casts are not needed in the users code . The second novel feature a ProxyDict provides is related to the fact that ProxyDict does not directly store the data. Instead, ProxyDict stores agents called proxies, and these proxies are queried for the data when the user makes a request. there are several consequences to this choice in the design. One reason for this choice in design is that it allows one to take the view that the dictionary is a list of data that a client might ask for but is not necessarily a deep copy of these data. In other words, the actual cost of formulating the data in the dictionary can be deferred until the time that a user actually requests it. For example, if the data in a dictionary are being retrieved from a database, then ProxyDict may be used to provide on-demand reading of data from the database. Another reason for using proxies is related to encapsulation. The user who is reading from the dictionary, does not need to know what proxies have been loaded into the dictionary. The user simply requests the desired data and the data are returned. A separate agent in the system can be responsible for loading proxies into the dictionary. In this way the choice of proxies maps onto the choice of input method or data formulation and the data consumer is completely insulated from this choice. The reader may be familiar with the facade pattern [1] used in object oriented programming. In a way, ProxyDict is the exact opposite of this. In the facade pattern a collection of classes act to duplicate or mimic some other application program interface. ProxyDict is the opposite in that it hides this other application interface and presents to the user, instead, a dictionary interface to the data. One might call ProxyDict a Dictionary Adapter Pattern. It hides some other data .cxxess API or hides a data model's hierarchical organization and replaces it with flat, dictionary-style semantics. One reason for doing this is to encapsulate the data model, removing the navigational knowledge from data clients and hiding it in the proxies. This protects the system from future data model evolution. ProxyDict .cxxomplishes these things through the interaction of several groups of classes. These groups are the front end, the dictionary, keys, and proxies. The dictionary itself has few or no methods of interest to a user. Instead, the interesting behavior is in the front end. The front-end includes methods for putting and getting items in the dictionary, but as we will see later, you can create front ends with whatever methods and semantics you need. The most important function of the front end is to provide type safety . How it does this will be explained later. Keys are used to select desired data. Keys are a rendezvous mechanism between the agent putting proxies into the dictionary and agents getting data from the dictionary. Proxies have already been described briefly. This document will elaborate on these ideas. It is meant to be a technical description of how the ProxyDict package functions, and is not intended to be a users' manual. At this point, it may be useful to describe the different kinds of users of ProxyDict. Some users will need to write new proxy classes to be placed into the dictionary. This might be a frequent .cxxurrence in a project, but it will be less frequent than the need for users to get data from the dictionary. Even less frequent will be the need to create new front ends. As will become clear in the sequel, this forms a hierarchy of users. The front end establishes the semantics for .cxxessing the dictionary and in addition specifies the kinds of proxies that can be used with this particular front-end . The person writing this front-end will need to write documentation to explain how these proxies are to be written and explain how data are to be gotten and put into the dictionary. This manual is directed towards these sorts of programmers although all users will find it useful to read the section about keys. Keys Keys are simple. They are objects that can be compared for equality and for less than. Given two keys, A and B, one can ask whether they are equal by simply using the expression, "A = = B." The base class for keys is called IfdKey. The abstraction being introduced here is that one can compare two keys without knowing whether not the underlying types are comparable. To understand this better, consider two derivatives of IfdKey, namely IfdIntKey and IfdStrKey. IfdIntKey represents integers. IfdStrKeys represent strings. Two IfdIntKeys are equal if, and only if, the integers they contain are equal, and similarly for IfdStrKeys, which contain strings. An IfdIntKey can never equal an IfdStrKey although they can both be compared via their common base class, IfdIntKey. This can be used to form a composite key, as will be explained now. IfdCompositeKeys make use of the composite pattern. A composite pattern is one in which a class, typically a base class, contains one or more instances of that class. When a method in this class is called, that call was dispatched to all of the owned instances within that object. For example, a Drawable object may contain a list of Drawable objects. When the redraw method is called on such an object, it calls the redraw method in all of its owned Drawable objects. IfdCompositeKey is an IfdKey by inheritance. IfdCompositeKeys contain a list of IfdKeys. When two IfdCompositeKeys are evaluated for equality, the keys within the two are compared one by one for equality and then a final result is computed. Notice that the abstraction, IfdKey, allows the definition of IfdCompositeKey in terms of this recursion. Had we not related IfdIntKey and IfdStrKey, as well as other keys to be described later, but had left them as independent, unrelated classes, then IfdCompositeKey would have been difficult to define. Why do we care about IfdCompositeKeys? We care about them because dictionaries often have the concept of a primary key, a secondary key, a tertiary key and so forth. Clearly if one defines a dictionary with only a primary key one is in jeopardy of having a use case later that requires a secondary key. IfdCompositeKeys provide for extensibility in this regard. if an interface is defined in terms of IfdCompositeKey, then it has fully encapsulated all degrees of cardinality of keys. Another key following the composite pattern is the dictionary key, IfdDictKey. Dictionary keys are special in that their constructors are private. The section on type safety below explains how dictionary keys are used to provide type safe .cxxess to the dictionary. Type keys, IfdTypeKey, represent C++ types. Two IfdTypeKeys, A and B, are equal if, and only if , the template arguments used in declaring them are the same. IfdTypeKeys share a common base class called IfdTypeKeyIFace. This base class allows an interface to receive a type key in the abstract. As another example of the use of the base class, all IfdDictKeys have an IfdTypeKeyIFace as the first key in the composite. Here we make detailed comments about the present implementation of keys. The code is presently written using a union. In addition, there is an enumeration variable that represents the specific kind of type key that a specific instance actually is. The user would expect these things to be implemented by using a virtual method, which is overridden appropriately and each derivative class. This approach was rejected for two reasons. First it ended up leading to further casts within the key code, which I wished to avoid. Second, the virtual method led to significant performance degradation when keys or dictionaries were used in loops. as an additional comment, the present relationship between composite keys and dictionary keys is such as to violate Liskoff's principal. This will be remedied in a future release. Figure one shows a class diagram for keys. Figure two shows an interaction diagram[2] explaining how composite keys work. Type safety Type safety is .cxxomplished through the interaction of several components in the system. It requires the close collaboration of the front-end, the dictionary, dictionary keys, type keys, and usage of C++ template mechanisms. When storing heterogeneous data in a dictionary, it is unavoidable that some kind of cast must be used. The goal in ProxyDict is to completely remove all such casts from the user code. Also, we wish to do this in a maintenance free way. In other words, we do not want to be made to create a new dictionary-related class for each C++ type stored in the dictionary nor, if possible, do we wish to inherit from a base class in order to make data storable. The cornerstone of type safety in ProxyDict is the dictionary key. As explained above, dictionary keys are like composite keys except that they have a private constructor. By convention, the dictionary key always begins with a type key, and this type key represents the type of data that is stored in the dictionary under this key. The get method in the dictionary takes a dictionary key as an argument; however, since no one can create a dictionary key, because its constructor is private, nobody can .cxxess the dictionary. There is exactly one exception to this: a base class for all front ends is able to create dictionary keys. Front ends are written, as described below, in a way to insure correct pairings of conversions of T* to void* and then from the void* back to T*. To illustrate how a front end works and to illustrate the interplay amongst it and the other classes, we will use a specific example of a front end called Ifd. Front-ends are always class templates. Ifd < T > is fairly fat, so we shall only consider two of its methods: static T* Ifd::get( IfdProxyDict *d, const IfdKey &k); static bool Ifd::put(IfdProxyDict *d, IfdDataProxyTemplate *p, const IfdKey &k); The dictionary abstract base class, IfdProxyDict, has a put method and a get method: virtual void* IfdProxyDict::get( const IfdDictKey& k, AbsArg& a ); virtual bool IfdProxyDict::put( const IfdDictKey& k, IfdDataProxyIFace* p ); The proxy abstract base class, IfdDataProxyIFace has a get method: virtual void* IfdDataProxyIFace::get( IfdProxyDict*, const IfdKey&, AbsArg& ) There is a derivative of IfdDataProxyIFace called IfdDataProxyTemplate which itself is a base class that will be described fully later. For this example, we will use a derivative of IfdDataProxyTemplate, called IfdDataProxy. An IfdDataProxy simply owns an instance of a T and returns a pointer to it. IfdDataProxyTemplate is more general and allows a fault hander to find the T. There are other methods besides these, but these will suffice to explain the essential data flow. One class, AbsArg, has not been described yet and will not be described until later. The base class for front ends, IfdProxyDictFrontEnd has the method: static IfdDictKey* IfdProxyDictFrontEnd::newDictKey(const IfdTypeKeyIFace&, const IfdKey& k); The basic idea is the following and is illustrated in Figures 3 and 4. Users may only .cxxess the dictionary through a templated front-end class, here Ifd. When a put() is done, the front end Ifd insists, via the signature of its put() method, that an IfdDataProxyTemplate, be given. In Figure 3, step 0, the client creates an IfdDataProxy called tProxy to hold the data, myT, which is of type T*. Thus, in step 1, Ifd is certain that it is receiving a proxy that returns objects that are actually T*'s. Next, in step 2, Ifd creates an IfdTypeKey1 and then calls IfdProxyDictFrontEnd::newDictKey to create a dictionary key in steps 3 and 4. The type key is passed in to newDictKey and it is impossible to create a dictionary key without this argument. Since newDictKey is the only agent in the system that can make a dictionary key, we are certain that the first component of any IfdDictKey will be an IfdTypeKey. The final step of key preparation is in step 6 where the user's key is added to the dictionary. Notice that steps 2 through 4 are only done on the first invocation of Ifd::put in order to improve performance. The reset step, step 5, is where future calls begin. Reset simply removes any previous user key that was placed in the statically held dictionary key. Having built a storage key in steps 2 through 6 that comprises the user's storage key, k, plus a data-type key generated by Ifd, that key is used to store the proxy, tProxy in step 7. The dictionary first does a search, in step 8, to locate a prior usage of the key and, if that search fails (duplicate data under any given key is forbidden) the proxy is added to the dictionary. To summarize where we are now, we have used the front end, Ifd, to restrict the dictionary's ability to store arbitrary IfdDataProxyIFace instances to derivatives that can only store a T. We formulated a dictionary key that augmented the user's storage key with type information, and IfdTypeKey, that was derived from the template parameter T and, finally, stored the whole mess in the dictionary. Notice here a pattern that will be common in ProxyDict. The dictionary itself works in terms of generic items, e.g., non-specific (with respect to type) base classes, like IfdDataProxyIFace and void*, but the front end Ifd restricts operations to classes specific to a given type, T. Now let's fetch the data back, as shown in Figure 4. The formulation of the dictionary key in steps 2 through 6 is identical to that in Figure 3. Thus, when the get and find are done in steps 7 and 8, if an IfdDataProxyIFace is found to be associated with the dictionary key, then Ifd knows that there is a type match since it is has slipped the IfdTypeKey into the dictionary key both for the store and the lookup. The void* pointer returned in step 9 is thus known to be convertible to a T*, which is done via a cast in step 10. This example, using Ifd , shows how a front-end is expected to use its template parameter, T, to enforce a type-relationship between the type stored by a proxy and the key under which that proxy is stored. The former is done by only .cxxepting T-specific proxies in the signature of the front-end's put method and the latter is done by augmenting the users key with type information in a structure, a dictionary key, that only a front-end can create. The front-end is a gate-keeper. Notice that because this is done with template classes, the compiler is doing a great deal of work for us. First, the compiler is the one that worries whether any two tokens, T and U are the same class type, perhaps appearing different only because of typedefs and macros. We thus leverage the compiler's strong typing. Next, the type keys, Ifd, and proxy specializations are automatically instantiated for us by the compiler. This satisfies our requirement, above, that we not need to write new dictionary classes in order to store new kinds of data. The user utters Ifd or some templated proxy, and the compiler does the rest. AbsArgs AbsArgs are replacements for using void* for generic data in an interface and perhaps a replacement for var args. To motivate this, consider a class, List, that provides a linked list of T objects. Suppose we wish to provide List with a method, apply( funcPtr f ), that will iterate over nodes in the list and apply the function f to each node in the list. This is usually done by typedef'ing funcPtr to typedef void (*funcPtr)( T* ) in other words, funcPtr is a pointer to a function taking a T* and returning nothing. The problem here is that often one wants to allow f to take more arguments than just the T. One way this is handled is by doing something like List::apply( funcPtr f, void* a); typedef void (*funcPtr)( T*, void* ); where the idea is that the user will take some desired argument, cast it to void*, pass that to apply() and then apply will pass it to funcPtr with each T* and funcPtr will cast it back to what it expects as an argument. The problem here is type safety. If the caller and funcPtr don't agree on who wants what then someone gets to stay up late with the debugger. AbsArgs help solve this problem. There are three relevant classes, AbsArg, AbsArgVal, and AbsArgCast. AbsArgVal inherits from AbsArg. In this pattern, AbsArg takes the place of void* and AbsArgCast takes the place of the cast. If one wished to write a function with generic arguments one would declare it to take an AbsArg, e.g., void someFunction( AbsArg& a ); To pass in data, one would do: MyClass m( someInitializer() ); AbsArgVal< MyClass> arg( m ); someFunction( arg ); Inside someFunction, the code would look like, void someFunction( AbsArg& formalArg ); FooClass* f = AbsArgCast< FooClass >::value( formalArg ); if ( 0 == f ) { // actual arg was NOT a FooClass } else { // actual arg was a FooClass. Use it. The reader likely feels this is very much not Object Oriented. The user is correct. Creation of functions that make big switch statements based on AbsArgCast are a bad idea. However, one .cxxasionally has .cxxasion where either a generic argument must be passed or an interface must be made non-open [3] and fat. In this case, AbsArg allows for closure of the interface while maintaining type safety and extensibility. Why not use dynamic cast? First, we did not have that at the time in our compiler. Also, AbsArg does not require T to have a virtual table, does not require any inheritance relationships, and is faster. AbsArgs are used in ProxyDict to allow extra information to be passed to Proxies. The reader should note that AbsArgVal does not take ownership of the T. Think of it as a wrapper around the T. Support for Caching Proxies If a proxy must perform an expensive operation to load its data, then it is natural to want to cache the result to reduce the cost of future reads. The proxy must then have a mechanism for determining its cache validity. In general, this is specific to exactly how the proxy gets its data, but there is one situation in which the dictionary itself must be involved. In this use case there is an outside agent that is using the dictionary itself to hold proxies and the outside agent has knowledge that can pace the proxies. An example may be useful. Suppose a dictionary is used to hold time dependent calibration information and that another dictionary is used to hold data taken from the device. Suppose the device is a sampling (in time) device and that at any given instant one sample (one event) is in the data dictionary. That dictionary may be .cxxessed many times for each sampling loaded, but since that sample corresponds to a fixed point in time, the calibration dictionary proxies have valid caches for every .cxxess to the data dictionary. But, when a new sample is loaded into the data dictionary, all proxies in the calibration dictionary now need to worry about being out of date. The data loader is the agent that has the knowledge that a new sample was loaded and we would like to distribute this to all proxies in the calibration dictionary. The dictionary base class, IfdProxyDict, supports this via its virtual method, virtual void IfdProxyDict::testProxyCaches(void)=0 which makes use of virtual void IfdDataProxyIFace::testCache( void )=0 IfdDataProxyIFace. When testProxyCaches is called, all proxies in the dictionary have testCache() called. This is the most abstract level of cache support in the dictionary in that all methods are pure virtual. ProxyDict provides a proxy base class that implements a Template Pattern [1] for implementing testCache. This base class is IfdDataProxyTemplate. The template pattern is via the proxy's get method, virtual void* get(IfdProxyDict* d, const IfdKey& k, AbsArg& a ) and makes use of two new pure virtuals added to this class: virtual T* faultHandler( IfdProxyDict*, const IfdKey&, AbsArg& )=0 virtual void testCache( void ) = 0; and the non-virtual methods bool cacheIsValid() { return _cacheIsValid; } void setCacheIsValid() { _cacheIsValid = true; } void setCacheInvalid() { _cacheIsValid = false; } The template algorithm is trivial. An IfdDataProxyTemplate has a pointer to a T, i.e., has a cache. When a get is done, cacheIsValid() is checked, and if the cache is valid, then the T is returned, otherwise faultHandler() is called and the result is put into the cache. Thus the template algorithm is the caching algorithm. Derivatives just have to implement faultHandler. faultHandler must call either setCacheIsValid or setCacheInvalid after it loads its data. If neither is done, the cache will always appear to be invalid but,once setCacheIsValid is called, the cache will remain valid until IfdProxyDict::testProxyCaches(...) is called. Here we encounter a subtlety in this pattern. IfdProxyDict::testProxyCaches calls testCache() in the proxy. What many proxies do is to immediately mark the cache as invalid without doing any computation at all. Then, the next time a get is done, and only then, they do a detailed evaluation of the cache. Notice this happens in the faultHandler. Why is this done? This is because a dictionary may contain a large number of proxies. Between each call to testProxyCaches, it may be that only a small fraction of the proxies are activated and it may be that the particular ones activated are different in each cycle. If we actually did a cache check on every proxy, we would likely be wasting time by checking proxies that end up not being called in the current cycle. Thus we simply set the cache invalidity flag as in indicator that, on next read, one must do a full check. In summary, ProxyDict provides synchronized cache invalidation capabilities and IfdDataProxyTemplate provides a template pattern for a caching proxy that uses a faultHandler to fetch data when dirty and which provides lazy evaluation of the cache status. Support for Storing Data There are applications, of course, in which clients wish not only to read data, but wish to modify and write data. Since ProxyDict acts as an adapter with dictionary semantics that is hiding some other data .cxxess mechanism, modifying data returned by a proxy will not be sufficient to cause that modification to be propagated back into the data store. ProxyDict includes capabilities to facilitate this. The methods in IfdProxyDict related to this are: enum StoreFlagControl { clearStoreFlags, keepStoreFlags }; virtual bool markForStore( const IfdDictKey& k, AbsArg& a)=0; virtual void storeAllMarked( const StoreFlagControl c = clearStoreFlags )=0; virtual void storeItem( const IfdDictKey& k, AbsArg& a); Also involved is a method in IfdDataProxyIFace: virtual void store( IfdProxyDict*, const IfdKey&, AbsArg& )=0; The idea is simply that, when a client modifies data and wishes those modifications to be propagated back to the data store, that client must call markForStore. Notice that markForStore has an IfdDictKey as an argument and thus users can not call this method directly since they can not create dictionary keys. Thus this call is done through a front end. Please review the section, Type Safety. As an example, the front-end in Ifd.h provides static bool Ifd::markForStore(IfdProxyDict *d, const IfdKey& k ); Calling markForStore does not cause storage to .cxxur. It simply posts a request. Some time later, the method storeAllMarked() must be called. This causes the dictionary to dispatch the request to store request to all proxies that have been marked via markForStore. The dispatch is via the void store( IfdProxyDict*, const IfdKey&, AbsArg& )=0 method from IfdDataProxyIFace. This post/execute approach is provided to allow for improvement in efficiency when such coordination is possible. There is also a simple storeItem method, shown above, that will immediately deliver the IfdDataProxyIFace::store(...) request to the associated proxy. Notice that the markForStore and storeItem both have an AbsArg in the argument list. This allows the client to pass in arbitrary, additional information that the proxy may need to know about. Notice that this implies, to some extent, that the client has some knowledge about the existence and capabilities of the proxy and thus introduces some lack of encapsulation. Nonetheless, the capability is sometimes needed. The StoreFlagControl argument in the storeAllMarked method determines whether the storeAllMarked clears the posted requests or not, e.g., if keepStoreFlags is given, then the request is posted to the proxies, but the set of "need to store" flags kept in the dictionary are *not* cleared, so that another storeAllMarked will cause another store. If StoreFlagControl has the value keepStoreFlags, then the AbsArgs passed in with the markForStore requests are not deleted. If it has the value clearStoreFlags, then the AbsArgs are deleted. Keys: Value semantics and clones. Hash Values. Abuses of Liskoff. Keys are odd in their copy semantics. Because IfdKey is abstract, and because its descendents vary in size, it is difficult to give IfdKeys copy semantics. Since we want to have the interfaces in terms of the base class, IfdKey, we end up with an interface that takes IfdKey& or IfdKey*. The interfaces in ProxyDict never wish to take ownership of the key for sake of convenience in the caller. Therefore we choose IfdKey&. IfdKeys have a clone method, that does a deep copy, for cases where lifetime is a concern. Right now, the public / private / protected declaration of clone() is in a confused state. Keys provide a hash value to improve performance in dictionaries. The hash value is returned as an int (oops). The number of hash buckets is set at compile time and right now is set to 1031 buckets. IfdKey::nHashBuckets() will tell you the value at run time. Programmers may decide to make new Key classes by inheriting from IfdKey, thus a few words on implementation are included here. The reader may note that IfdKey uses an enum and an union such that the actual data value is stored in the union in IfdKey rather than having getKeyKind() virtual and delegating the data to a derivative. The former is done for performance reasons. The overhead lost to the method call was too high for high performance applications. The reason for the ugly, non-OO union is that it lets us avoid a cast in the implementation of virtual operator==(). One either knows the kinds of the keys being compared and casts or selects in the union. Those are the only implementation options, short of a double dispatch visitor pattern, and I chose the union. A visitor pattern would be too slow. A result of this is that IfdKey is not as extensible to new types as I would like it to be because of the need to edit the keyKind enum. Such is the cost of speed. If you do create new keys, remember the basic rule: two keys can never be equal if they are of different types even if they share the same representation, e.g., IfdTypeKeys and IfdIntKeys are both represented by integers, which defines their equality, but a type key can never equal an integer key. At present there is some confusion in the IfdKey class declaration with regard to IfdCompositeKeys. There are times when it seems all of the composite key methods should be moved up into IfdKey but non-composite derivatives will simply give appropriate limiting case results. At other times this seems a violation of Liskoff's rule. What is coded now corresponds to some random state when the code hit production and needed to freeze. It can be clarified at a later time. Dictionary Keys Unlike IfdCompositeKeys, IfdDictKeys do not own the keys that get added to them. This allows dictionary keys to have a reset method that simply forgets about all added keys except for the initial type key. As shown in Figure 4, this allows a pattern of usage in the front-end such that dictionary keys do not need to be new'd and deleted on every call. This speeds up IfdSimpleProxyDict when used with Ifd.cxx by a factor of five. Right now, IfdDictKey inherits from IfdCompositeKey. This was a matter of convenience and is a gross violation of Liskoff's substitution rule. If this can be split without harming performance, it will be done in the future. Authors of front-ends must take care. Because dictionary keys do not own their added keys, you must watch for re-entrancy problems in your front-end. The concern is that a get call on your front-end may reach into a proxy in the dictionary that then itself manages to make a get call on the same dictionary with the same front end and thus overwrites the contents of the dictionary key. This is not itself a problem if the front-end and dictionary are done with the key at that point. You must evaluate this when you design your front end. Type Keys Type keys are the essential voodoo that allows ProxyDict to work. Here, they are explained. Type keys are instances of the class template, IfdTypeKey. IfdTypeKey has a private method, setMyId( void ). setMyId has a static instance of an IfdCounter class. Notice this static is held at method scope. To understand how this works, first consider the physical layout. Whenever a user refers to a specific type key, e.g., IfdTypeKey, the compiler specializes the class template and emits code. If templates are managed correctly, there is exactly one code specialization linked into the code image. Here the reader must take care not to confuse a specialization of the template with an instance of the specialization. Even if more than one place in the program creates an IfdTypeKey, only one specialization (one ``hunk of executable code'') is made even though there are multiple instances. Put another way, different instances of IfdTypeKey have different allocations to hold their instance _data_ but they all _share_ the executable code for IfdTypeKey. Thus when the compiler processes the source code, it will allocate an IfdTypeKey::setMyId() for each distinct T in the program, and each setMyId will have space allocated for an IfdCounter. IfdCounter is a simple class that just counts the number of times it has been instantiated. It does not count the number of instantiations! If you create 5, destruct 5, then create another, the scaler will have the value of 6, not 1! In summary, all instances of IfdTypeKey, for a given X, share a common IfdCounter and thus all have a common IfdCounter value that can not, by construction, equal the value associated with IfdTypeKey with X != Y. The evaluation of ``X != Y'' is carried out by the compiler and so, by definition, is correct. There is no guarantee that two invocations of a program will generate the same mapping between a type, T, and its actual integral value in the IfdTypeKey. Thus they can not be used for persistence. There is a common base class, IfdTypeKeyIFace, for all IfdTypeKey. This allows programmers to express the idea that a ``key conveying type'' is needed in an interface in a generic way. The reader may notice some macros in IfdTypeKey.cxx called IFDTYPEKEY_SPECIALIZE. Ignore them. They are non-functional but may be made to work in the future. Alias Proxies Alias proxies simply defer their evaluation to another proxy. The idea is that you wish to advertise the availability of some data of type T under some key, but you wish to be able to swap in and out which of several data are returned. You would create your normal proxies for type T and insert the various options under several keys, say ``Key1,'' ``Key2,'' and ``Key3.'' Users would be told to use T's in the dictionary .cxxording to some other key, say ``Default.'' You then insert an alias proxy for T's under this key, ``Default'' and set the alias proxy so that it defers to which of Key1, Key2 or Key3 you want to have active at the moment, say Key2. When the user asks the dictionary for T using key ``Default,'' the alias proxy will be found. The alias proxy resolves the request by calling into the dictionary for a T under key Key2. The T that comes back is then returned to the user by the alias proxy. So, Alias proxies implement indirection on the key name. Writing New Front-Ends $$ note written yet. Ownership Issues Here we state the memory management policy in ProxyDict. There is none in the dictionary and none in IfdDataProxyIFace. Specific implementations, however, must make the policy clear. For example, IfdSimpleProxyDict, a sample dictionary implementation, has the policy that all proxies added to the dictionary are owned by the dictionary. IfdDataProxyTemplate has the policy that it owns the T that it represents. It also assumes that calling delete on T is sufficient to fully remove all of T's memory, i.e., that if T is a complex object, then T itself establishes its memory management policy via its destructor. (This often is not the case for container classes). AbsArgVal do not own their T's. Ommissons/Problems/Philosophies Users can NEVER get their hands on a proxy. Can't delete a proxy. NB alias proxies. Proxy is a bad name. Brief Class Descriptions Core Classes IfdProxyDict.h Abstract base class for all dictionaries. IfdProxyDictFrontEnd.h Abstract basec lass for all front-ends. IfdSimpleProxyDict.h Hash based implementation of a ProxyDict. IfdSimpleProxyDictEntry.h Storage element in an IfdSimpleProxyDict. AbsArgs Void* and varargs replacements AbsArg.h Generic, non-typed datum. AbsArgVal.h AbsArgVal holds a T*. AbsArgCast.h Safe AbsArg->AbsArgVal conversion. Front-Ends Ifd.h Front end used in BaBar offline. Keys IfdKey.h Base class for all keys. IfdCompositeKey.h IfdStrKey.h IfdTypeKeyIFace.h IfdTypeKey.h IfdCounter.h IfdDictKey.h IfdIntKey.h IfdKeyHash.h Proxies IfdDataProxyIFace.h IfdDataProxyTemplate.h IfdAliasProxy.h IfdDataProxy.h IfdHepAListProxy.h IfdHepAListProxyTemplate.h* Defunct classes IfdData.h IfdHepAList.h GNumakefile.plain Writing New Front Ends Test Programs testProxyDict.t IfdTestClasses.h AbsArgTest.cxx IfdHepAListTest.cxx IfdKeyTest.cxx IfdNoCompileTest.cxx IfdSimpleProxyDictSpeedTest.cxx IfdSimpleProxyDictTest.cxx IfdTestClasses.cxx IfdTypeKeyTest.cxx AbsArgTest.results IfdSimpleProxyDictTest.results IfdHepAListTest.results IfdTypeKeyTest.results IfdKeyTest.results End Notes [1] ``Design Patterns: Elements of Reusable Object-Oriented Software'' Gamma, Helm, Johnson, Vlissides. Addison-Wesley, Reading MA. 1995. [2] ``Instand UML'' Pierre-Alain Muller. Wrox Press, Ltd. BirmingHam UK. 1997. [3] Betrand's Open/Closed principle. See, for example, R.C. Martin's book (need exact imprint...).