EDM Design Notes Containers, Templates, and ROOT I/O begun 30 May 1999 revised 14 June 1999 1) A RefVector::copy constructor makes a copy of all elements of the argument ReferenceVector. Suppose those items are stored? The copies are *not* automatically stored. They are always left unstored by copy c-tor. The "copied" elements, for instance, do not have their object_id set since they are unstored. Consider disallowing copying outright... avoid possible surprises. 2) Once stored, the container cannot be appended to. This is implemented by design since insert/append are non-const methods. The container, once stored, can only be accessed through a ConstHandle. 3) Can a container get into a state where some items are stored and some are not? This affects the Streamer() for the container, since one would not wish to store another copy of the already stored element of the container. With non-growable containers, the insert/append methods may have to check that an item is not stored (undefined oid) before permitting it to be in the container. Consider overlapping RefVectors... who "owns" elements, who streams them out. Look at difference with view in streaming. I think it would be best if RefVectors always own what they hold pointers to, and stream themselves out by "value" of the thing pointed to. ViewVectors never own what they hold pointers to, and stream themselves out using Link<>s. 4) Containment or Inheritance: I have chosen containment, the traditional choice, though this has its draw-backs with the STL. Only those algorithms which I put into the interface of my containers by hand can be used since my containers are not compatible with generic algorithms. The advantage of this is that I can add significant number of sanity checks on the algorithms and especially the iterators to detect and report common programming mistakes. I also have the advantage of controlling what one can do to a stored container by choosing to remove const from unacceptable algorithms. Suppose we have: const vector<>& contents(void) const ; vector<>& contents(void) ; method and use this to re-use STL algorithms. Non-const for views, const for refvectors. Then one can manipulate the STL container/iterator directly, with the Edm container acting simply as an adapter. TYPES OF CONTAINERS: 1) ValueVector: Cannot contain StorableObjects. Stream out items in nodes. Items cannot be derived from StorableObject since STL algorithms require a publicly visible destructor and StorableObjects have a protected destructor. Items must have a Streamer() method, but do not have to derive from TObject. 2) RefVector: Can contain StorableObjects or non-storable objects. Stream out items pointed to by nodes by value. Items need not be StorableObjects, nor TObjects, but must have a Streamer() method. 3) ViewVector: Can only contain StorableObjects. Stream out using Links for items pointer to by nodes. But since Link can only be made for StorableObjects, items must be derived from StorableObject. 4) IndexedViewVector: Can contain composite objects of StorableObjects or non-storable objects. Stream out IndexLinks for items. IndexLinks point to a StorableObject container or composite, which contains StorableObjects or non-storable objects referenced by a simple integer index. 5) GrowableRefVector, GrowableViewVector: Like RefVector and ViewVector. Store by reference. May store individual nodes in record rather than storing itself as one contiguous object. Consider container of {Ids, limits} to associate specific Ids to states (stages of building up) of container. 6) GrowableKeyedRefVector: Like GrowableVector, but with a Key (possibly composite, as in RRL3 applications) for permit specialized searches and associations. 7) SpecializedContainer (a stereotype): A complete custom container not matching the interface or organization of the STL. Must supply its own Streamer(). ROOT I/O ISSUES To implement ROOT I/O for a class, there are four steps that can be taken: a) define a Streamer() method which serializes your object to/from TBuffer --- b) put a ClassDef() macro in your class interface c) put a ClassImp() macro in your compiled source code d) derive your class from TObject --- e) stream out your class's immediate base class in the Streamer() Tthe *minimal* requirements for ROOT I/O depend on whether the content of the I/O Stream is known ahead of time or not. For instance, if I know the next object is the Id corresponding to the _next_id data member of the oid_manager (EventRecord class knows an Id is second in its Streamer() after TObject base class piece), then for this application the Id class only needs a Streamer() which the "knowledgable" class can use. Id does not have to derive from TObject, nor does it need to contain a ClassDef() and ClassImp() macros. If no object can predict what will come next in the I/O stream (EventRecord class does not know which objects come after the Id instance), then the full ROOT I/O dictionary mechanism must be used, which requires the ClassDef(), ClassImp() macros, and derivation from TObject. If your class needs its base class data members to be made persistent, then your class needs to stream out your class's immediate base class in your Streamer(). This is completely optional and can be decided on a class-by-class basis. Root I/O cannot handle templates in all possible cases. If we were to implement a template class RefVector, then that class cannot contain a ClassDef() or ClassImp() macro. These macros will either expand to uncompilable non-sense (as with user-defined namespaces) or will produce unusable dictionary entries. However, one can define a class CdfTrackSet which contains a RefVector. Rootcint will scan the header file and will properly generate the boilerplate for the dictionary, though the user is forced to write the Streamer() method by hand (a simple prescription exists for this). So the approach we are taking with ROOT I/O for higher-level physics objects will not work right now for containers. We cannot simply generate the class plus a Streamer(), insert ClassDef() and ClassImp() in the appropriate places, and derive the class from StorableObject (and therefore from TObject). We need an adapter to high the template nature of the container class from ROOT I/O... an instantiated template class is the same as any other class. We propose that this adapter be able to return a reference (and const reference) to the underlying Edm or STL container to permit the transient form of the object to have all the STL machinery available to it, with requiring that the developer implement an STL-compliant container (hard enough project by itself) which ROOT I/O can handle.