Revised EDM Model Notes 25 February - 02 March 1999 Rob Kennedy I) Minimize copies in transient <=> stored representation -) Have insert() store ObjectIds into things passed to it II) Revisions to Collections APIs to reduce coupling ------------------------------------------------------------------------------- I) Minimize copies in transient <=> stored representation Suppose there is class Muon which contains data members of built-in data type and a one-way association to a class Track, implemented with a smart StorablePointer instance: template class StorablePointer { TYPE* _p_item ; // TRANSIENT EventRecord* _p_record ; // TRANSIENT ObjectNumber _number ; static ObjectName name(void) { return(ObjectName("TYPE")) ; } // or equivalent stream_read() ; stream_write() ; setPointer(TYPE*) ; setPointer(RecordIterator) ; // downcast to TYPE* from StorableObject* setId(EventRecord*, ObjectId) ; // Can have _p_item active, _number active, or both // _p_record is NULL: object pointed to is not stored, so _number inactive } ; class Muon { StorablePointer _p_track ; activate(void) ; deactivate(EventRecord*, const ObjectId&) ; } class GenericStorableObjectPointer: public SmartRefCntPointer { StorableObject* _p_generic_object ; constructor(const SpecificStorablePointer& p_specific_object) { _p_generic_object = static_cast(p_specific_object) ; } operator *() ; operator ->() ; // StorableObject API only... objectName(), objectNumber() bool is_valid(void) const ; // Smart RefCnt pointer machinery } ; template class SpecificStorableObjectPointer: public SmartRefCntPointer { TYPE* _p_specific_object ; constructor(const GenericStorablePointer& p_generic_object) { _p_specific_object = dynamic_cast(p_generic_object) ; } // TYPE must be child of StorableObject for dynamic_cast to work operator *() ; operator ->() ; // TYPE's API available bool is_valid(void) const ; // Smart RefCnt pointer machinery } ; // The EventRecord is a restricted list of GenericStorableObjectPointers. bool EventRecord::insert(const GenericStorableObjectPointer& p_item) { _list.insert(p_item) ; return(true) ; } class StorableObject { virtual bool activate(void) = 0 ; virtual bool deactivate(EventRecord* p_record, const ObjectId* p_objectId = 0, const int n_objectId = 0) = 0 ; } ------------- Track aTrack(...) ; // Track derived from StorableObject SpecificStorableObjectPointer p_muon = new Muon(&aTrack, ...) ; ObjectId muonId = p_record->insert(p_muon) ; // up-cast to GenericStorObjPtr float pt = p_muon->pt() ; ObjectId trackId = p_record->insert(aTrack) ; p_muon->deactivate(p_record, &trackId, 1) ; { _p_track.setId(p_record, trackId) ; } ObjectId muonId = p_record->insert(p_muon) ; // And now in a different module RecordIterator muonIter = p_record->find_first("Muon") ; SpecificStorableObjectPointer p_muon(muonIter) ; float pt = p_muon->pt() ; p_muon->activate() ; { ObjectId trackId = p_muon->p_track().objectId() ; RecordIterator trackIter = p_record->find(trackId) ; _p_track.setPointer(trackIter) ; } This setPointer() method here hides a downcast from StorableObject* to TYPE*, just as the SpecificStorableObjectPointer constructor (GenericStorObjPtr) hides one. This is the price to be paid to go from the generic classes RecordIterator and GenericStorableObjectPointer that only exhibit traits related to StorableObjects to the classes with specific object APIs, StorablePointer and SpecificStorableObjectPointer. The naming scheme should be cleaned up to avoid confusion, since some names are rather similar for very different classes. --------------- Consider the following case, however (from D0 experience): Track aTrack(...) ; // Track derived from StorableObject Muon* pointer_to_muon = new Muon(&aTrack, ...) ; // bare pointer SpecificStorableObjectPointer p_muon = pointer_to_muon ; ObjectId muonId = p_record->insert(p_muon) ; ObjectId trackId = p_record->insert(aTrack) ; p_muon->deactivate(&trackId, 1) ; ObjectId muonId = p_record->insert(p_muon) ; delete pointer_to_muon ; This releases the memory that something in the record points to. Jim K's has been looking into preventing this by controlling how that the SpecificStorableObjectPointer<> instance can be constructed. For instance, one can use a memory allocation factory in the EventRecord class: Track aTrack(...) ; // Track derived from StorableObject SpecificStorableObjectPointer p_muon = p_record->allocate(1) ; ObjectId muonId = p_record->insert(p_muon) ; ObjectId trackId = p_record->insert(aTrack) ; p_muon->deactivate(&trackId, 1) ; ObjectId muonId = p_record->insert(p_muon) ; // Nothing is left to be deleted erroneously // Crude, off-the-cuff implementations... do not take seriously SpecificStorableObjectPointer EventRecord::allocate(const int n_instances) { size_t allocation_size = sizeof(TYPE)*n_instances ; void* p_allocation = 0 ; if (allocation_size < 256) { p_allocation = _allocate_from_small_pool(allocation_size) ; } else if (allocation_size < 4096) { p_allocation = _allocate_from_medium_pool(allocation_size) ; } else if (allocation_size < 65536) { p_allocation = _allocate_from_large_pool(allocation_size) ; } SpecificStorableObjectPointer temp(*this, p_allocation) ; return(temp) ; } EventRecord::deallocate(SpecificStorableObjectPointer p) { void* p_allocation = p.where() ; size_t allocation_size = sizeof(*where) ; if (allocation_size < 256) { _deallocate_from_small_pool(p_allocation) ; } else if (allocation_size < 4096) { _deallocate_from_medium_pool(p_allocation) ; } else if (allocation_size < 65536) { _deallocate_from_large_pool(p_allocation) ; } return ; } The number of instances argument is useless in this crude implementation. I need to provide array allocator/deallocators which use arrays of SpecStorObjPtr. Does this particular API leads to a coupling of the StorableObjectPointer classes to the EventRecord? ------------------------------------------------------------------------------- 02-Mar-1999 Can I improve the robustness by storing the ObjectId inside Muon in insert()? class Muon: public StorableObject { StorablePointer _p_track ; setPointer(void) ; setLink(void) ; } ; // A transient Muon class ObjectId Edm::EventRecord:: insert(StorableObject& thing) // non-const argument since _number will change { if (thing.number() != ObjectNumber::Null) { // ERROR: This object already inserted!!! return(thing.number()) ; // avoids user reaction to this error } ObjectName name = thing.name() ; ObjectNumber number = acquireNextNumber(name) ; thing.setNumber(number) ; if (! store(thing)) { thing.setNumber(ObjectNumber::Null) ; releaseNumber(name, number) ; } thing.setReadOnly() ; return(number) ; } class StorableObject { friend EventRecord::insert ; public: ObjectName name(void) const ; ObjectNumber number(void) const ; private: void setNumber(ObjectNumber newval) ; void setReadOnly(void) ; private: // Never touch data members directly outside of class ObjectNumber _number ; == ObjectNumber::Null by default bool _is_readonly ; } ; // ModuleA //======== Track aTrack(...) ; SmartRCPointer p_muon = new Muon(&aTrack, ...) ; float pt = p_muon->pt() ; // Pointer has Muon API via op->() ObjectId trackId = p_record->insert(aTrack) ; p_muon->setLink() ; // Asks aTrack for its Id, and sets link ObjectId muonId = p_record->insert(p_muon) ; // insert muon // ModuleB //======== RecordIterator iter_muon(p_record, SelectByName("Muon")) ; ConstSmartRCPointer p_muon(iter_muon) ; p_muon->setPointer() { // code for setPointer() ObjectId trackId = p_muon->_p_track.objectId() ; // query link for Id RecordIterator trackIter(p_record, SelectById(trackId)) ; _p_track.setPointer(trackIter) ; // casts const StorableObject* in trackIter to const TYPE* in _p_track } At this pointer, both the Muon and the Track are stored in the EventRecord, but they linked with a transient pointer (which is not stored in the persistent representation, of course). Care must be taken to insure that the read-only nature of the stored objects is preserved. ------------------------------------------------------------------------------- II) Revisions to Collections APIs to reduce coupling After reviewing the current CdfTrackCollection implementation which is being adopted as the StorableCollection prototype, Jim K has made several suggestions to reduce coupling between collections, iterators, and predicates. LAST NOTES: for (Iterator iter = mygoldmus.begin(Muon::PtGreaterThan(4.0)) ; iter != mygoldmus.end() ; ++iter) { // treat muons with Pt > 4.0 } SUGGESTED: for (Iterator iter(mygoldmus, Muon::PtGreaterThan(4.0)) ; iter != mygoldmus.end() ; ++iter) { // treat muons with Pt > 4.0 } Note a) The STL algorithms tend to take as arguments either (collection, predicate) or (iter_begin, iter_end, predicate). Avoid EventRecord::begin(Pred). Note b) The STL containers have few member function manipulators. Most manipulators are external generic algorithms. Avoid EventRecord.find_first(). class Iterator { Iterator(Iterator begin, Iterator end, Predicate selector) ; Iterator(Collection entire_container, Predicate selector) ; } class Container { Iterator begin() ; Iterator end() ; } class ElementOfCollection { class Predicate ; class Comparison ; } for (Iterator iter1(my_muon_set, Muon::PtGreaterThan(4.0)) ; iter1 != my_muon_set.end() ; ++iter1) { for (Iterator iter2 = iter1.peek_ahead() ; iter2 != my_muon_set.end() ; ++iter2) { // treat distinct muon pairs, for muons with Pt > 4.0 } } ------------------- Similarly for Views, we should not have a collection know how to make a view of itself, but just the reverse. Check out the discussion of the Observer pattern in Design Patterns by Gamma et al. LAST NOTES: MuonSetView myhardgoldmus = mygoldmus.createView(PtGreaterThan(4.0)); SUGGESTED: MuonSetView myhardgoldmus(mugoldmus, PtGreaterThan(4.0), Muon::RankByPt) ;