The information about collections is organized in the following manner:
In order to implement collection functionality, the ObjectStore collection facility provides
Because collections are pointers to objects - rather than the objects themselves - an object can be contained in many different collections. Furthermore, collections can be used in transient or persistent memory, depending on the needs of your application.
The ObjectStore collection facility gives you a great deal of control over the behavior and representation of the collections you create. Other collection facilities allow you to iterate over the elements in a collection, and to query collections for elements meeting simple or sophisticated sorting criteria.
Requirements for Applications Using Collections
Note the following requirements for using ObjectStore collections:
If your application uses ObjectStore dictionaries, your program must include <ostore/coll/dict_pt.hh> and must also include <ostore/coll/dict_pt.cc> in any source file that instantiates an os_Dictionary, following the other header files. See ObjectStore Header Files in Chapter 2, Working with Source Files, of ObjectStore Building C++ Interface Applications.
Initializing the Collection Facility
Any program using collection functionality must first call the static member function os_collection::initialize(). Call this function after calling objectstore::initialize(). For example:
objectstore::initialize(); os_collection::initialize();
UNIX Platforms | Windows and OS/2 Platforms | |
---|---|---|
Collections | -loscol liboscol.soliboscol.ldb | ostore.lib os_coll.ldb |
Indexes and queries | -losqury libosqury.solibqry.ldb | os_query.ldb |
Using Persistent Collections
If you use persistent collections (whether parameterized or not) the actual representation that is used for storage in the database is an os_collection internal representation. You do not have to mark any collection type in your schema source file. Using Persistent Dictionaries
If you use persistent dictionaries, you must call the macro OS_MARK_DICTIONARY() for each key-type/element-type pair that you use. Calls to this macro have the form
OS_MARK_DICTIONARY( key-type, element-type)Specific information about marking dictionaries can be found in Marking Persistent Dictionaries. The OS_MARK_DICTIONARY() macro is described in the ObjectStore Collections C++ API Reference.
Thread Locking
If your application does not use multiple threads, disable collections thread locking by passing 0 to the following member of the class os_collection:
static void set_thread_locking(os_boolean) ;If your application uses multiple threads, see Chapter 3, Threads, in the ObjectStore Advanced C++ API User Guide.
Introductory Collections Example
Here is a simple example to illustrate how and when to use collections. ObjectStore collections provide some alternatives to linked lists, C++ arrays, and other aggregation data structures. In cases where your application uses functionality such as queries and ranges, collections are easier to use and more powerful.
#include <iostream.h> #include <string.h> #include <ostore/ostore.hh> #include <ostore/coll.hh> /* A simple class which records a note entered by the user. */ class note { public: /* Public Member functions */ note(const char*, int); ~note(); void display(ostream& = cout); static os_typespec* get_os_typespec(); /* Public Data members */ char* user_text; int priority; };
/* ++ Note Program - main file */ #include "note.hh" extern "C" void exit(int); extern "C" int atoi(char*); /* Head of linked-list of notes */ os_list *notes = 0; const int note_text_size = 100; main(int argc, char** argv) { if(argc!=2) { cout << "Usage: note <database>" << endl; exit(1); } /* end if */ objectstore::initialize(); os_collection::initialize(); char buff[note_text_size]; char buff2[note_text_size]; int note_priority; os_database *db = os_database::open(argv[1], 0, 0644); OS_BEGIN_TXN(t1,0,os_transaction::update) { os_database_root *root_head = db->find_root("notes"); if(!root_head) root_head = db->create_root("notes"); notes = (os_list *)root_head->get_value(); if(!notes) { notes = &os_list::create(db); root_head->set_value(notes); } /* end if */ os_cursor c(*notes); /* Display existing notes */ for(note* n=(note *)c.first(); n; n=(note *)c.next()) n->display(); /* Prompt user for a new note */ cout << "Enter a new note: "<< flush; cin.getline(buff, sizeof(buff)); /* Prompt user for a note priority */ cout << "Enter a note priority: "<< flush; cin.getline(buff2, sizeof(buff2)); note_priority = atoi(buff2); notes->insert(new(db, note::get_os_typespec()) note(buff, note_priority) ); } OS_END_TXN(t1) db->close(); }
Note that all the collection types described below (with the exception of os_Dictionary) have both a templated (parameterized) and a nontemplated (nonparameterized) version. For ease of differentiation, the templated versions use uppercase letters (for example, os_Set) whereas the nontemplated versions use lowercase (os_set). Nontemplated classes are always typed as void* pointers.
For information on
Besides lacking order, something that distinguishes sets from some other types of collections is that they do not allow multiple occurrences of the same element. This means that inserting a value that is already an element of a set either leaves the set unchanged or causes the signaling of a run-time exception (depending on the behavior you have specified for the set). In either case, sets disallow duplicates.
os_Collection and os_collection
Of the collection types, os_Collection (and os_collection) offer the most flexibility in making behavior changes during the lifetime of an instance. Creating an instance of the base class os_Collection gives you direct control over allowing duplicate elements and maintaining element order, the behaviors that distinguish sets, bags, and lists. os_Array and os_array
ObjectStore arrays are like ObjectStore lists, except that they always provide access to collection elements in constant time. That is, for all allowable representations of an os_Array, the time complexity of operations such as retrieval of the nth element is order 1 in the array's size. Arrays also always allow null elements, and provide the ability to automatically establish a specified number of new null elements. os_Dictionary and os_rDictionary
Like bags, ObjectStore dictionaries are unordered collections that allow duplicates. Unlike bags, however, dictionaries associate a key with each element. The key can be a value of any C++ fundamental type, a user-defined type, or a pointer type. When you insert an element into a dictionary, you specify the key along with the element. You can retrieve an element with a given key, or retrieve those elements whose keys fall within a given range.
Using a Decision Tree to Select a Collection Type
Here is a simple decision tree to help you choose a collection type to suit particular behavioral requirements
.
Collection Characteristics and Behaviors
Collections Store Pointers to Objects
ObjectStore collection classes store pointers to objects, not the objects themselves. Thus, elements exist independently from membership in a collection, and a single element can, in fact, be a member of many collections. Collections Can Be Transient or Persistent
Like all types in ObjectStore, collections can be used in transient memory (program memory) or persistent memory. Transient collections are used to represent transient, changeable groupings; the current list of cars in the parking garage, for example. Persistent collections contain more permanent associations, such as the list of people on a board of directors or the founding states of the European community. Parameterized and Nonparameterized Collections
Every ObjectStore collection class (except os_Dictionary) is provided in both a templated (parameterized) and a nontemplated (nonparameterized) form. See Templated and Nontemplated Collections.
Templated classes use uppercase letters in their class names (os_Set), whereas nontemplated classes use lowercase letters in their class names (os_set).
Class Hierarchy Diagram
The following diagram shows the hierarchical relationship among all the ObjectStore collection classes. Note that E is actually a pointer value: the element type parameter used by the templated collection classes to specify the types of values allowable as elements. See Using Collections with the Element Type Parameter for more information.
Collection Behaviors
The ObjectStore collection classes vary according to what behaviors and characteristics are permitted, prohibited, or permitted under some circumstances. The table below identifies default settings and behavior; however, you can customize many of these settings. You can, for example, change the default size of a collection and, in some cases, you can specify whether or not null elements can be inserted into the collection. See Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide for more information on customizing your ObjectStore collections.
Note that os_Dictionary differs substantially from other collections classes in its behaviors. Dictionary behaviors are related to the key of an element rather than to an element itself. See Dictionaries for information on how ObjectStore dictionaries differ from other ObjectStore collection classes. See also Creating Dictionaries.
Expected Collection Size
By default, all collection classes are presized with a representation suitable for a size of less than 20. The expected_size argument for the collection create() functions lets you supply a different default size. For more information, see Specifying Expected Size in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.
Performing pick() on an Empty Set
For all collection classes, performing pick() on an empty collection or on an empty result of a query of a list or collection raises an err_coll_empty error. Collection Representations
The default representation policies for ObjectStore collections differ depending on whether the collection is created with the create() function or is embedded within another object. Collections created by create()
The representation types listed in the following table are described in detail in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.
Representations for embedded collections
The representation types listed in the following table are described in detail in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.
Note that expected_size determines the collection's initial representation. So, for example, if you set the expected_size for an os_Set to 21, os_dyn_hash is used for an os_Set's collection's entire lifetime. (This can, however, be customized; see Customizing Collection Representation in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide).
Representations used for os_Dictionary are discussed separately in Dictionary Representation.
Templated and Nontemplated Collections
ObjectStore collection classes are provided in both templated (parameterized) and nontemplated (nonparameterized) versions. Using Collections with the Element Type Parameter
The parameterized ObjectStore collection classes - os_Set, os_Bag, os_List, os_Collection, os_Dictionary, and os_Array - are actually class templates. Each class has a parameter, the element type parameter, that specifies the type of value allowable as elements. This type must be a pointer type. For example:
os_Set<part*> &a_part_set = os_Set<part*>::create(db1) ;Defines a variable whose value is a reference to a set of pointers to part objects. The name of the element type, part*, appears in angle brackets when the collection type is mentioned. (Note that the element type parameter is represented as <E> in function signatures.)
#include <ostore/ostore.hh> #include <ostore/coll.hh> class part { public: int part_number; os_Set<part*> &children; employee *responsible_engineer; part (int n) : children(os_Set<part*>::create(db1)){ part_number = n; responsible_engineer = 0; } };
Notice that the names of the parameterized classes have an uppercase letter following the os_, while the nonparameterized classes have a lowercase letter following the os_. Notice, as well, that there is no nonparameterized version of os_Dictionary.
Most of the examples in this manual use the parameterized interface, but if you are not using parameterization, just drop any construct of the form
< element-type-name>and use the nonparameterized collection type names (beginning with os_ followed by a lowercase letter). The os_Set class definition example in Using Collections with the Element Type Parameter would look like this:
class employee ; extern os_database *db1 ; class part { public: int part_number ; os_set &children ; employee *responsible_engineer ; part (int n) : children(os_set::create(db1)){ part_number = n; responsible_engineer = 0 ; } };The <part*> is left out after each occurrence of os_Set, and os_Set is changed to os_set.
The create method is a wrapper for constructors. For example:
static os_Collection<E> & os_Collection<E*>::create( os_database *db, os_unsigned_int32 behavior = 0, os_int32 expected_size = 0 )These wrappers return a reference to a new, empty collection.
Do not use new to create collections.
Generally, it is preferable to use the create() function for each type of collection, as it results in better performance and better locality of reference because of its underlying optimization for mutation.
Each collection class has a destroy() function that deletes a specified collection. See Destroying Collections for more information. You can also call delete() to destroy a collection created with ::create().
Use a constructor only for stack-based collections or collections embedded directly within another class. However, avoid embedding collections directly. Instead, embed collections as pointers or references, and call the create() function in a constructor initialization list.
create() functions
Each collection function has four overloadings, as described in the ObjectStore Collections C++ API Reference. The first argument, which specifies where to allocate the new collection, is the only required argument. Depending on the overloading, create() functions for collection types specify a database, segment, object cluster, or proximity to another object. Constructor functions
The overloadings of constructor functions are listed in the ObjectStore Collections C++ API Reference. For each constructor, the first two overloadings create an empty set. The second overloading lets you specify the expected collection size (see Expected Collection Size). The last two overloadings are copy constructors, creating a collection with the same membership as another specified collection.
Customizing collections
Many of the characteristics and behaviors of various types of collections can be modified. For general information about each class, see the following sections in the ObjectStore Collections C++ API Reference.
Collection Type | Parameterized Class | Nonparameterized Class |
---|---|---|
Array | os_Array | os_array |
Bag | os_Bag | os_bag |
Collection | os_Collection | os_collection |
List | os_List | os_list |
Set | os_Set | os_set |
You can create dictionaries with os_Dictionary::create() or an os_Dictionary constructor. Use the constructor only for stack-based arrays, or arrays embedded directly within another object. If you want to use a reference-based dictionary, use the os_rDictionary functions.
The os_Dictionary::create() overloadings require not only the element type parameter but the key type parameter as well. See Using Collections with the Element Type Parameter for information about the element type parameter, and "Key type parameter", below, for information about the key type parameter.
os_Dictionary::
The os_Dictionary::os_Dictionary() constructor is described in the ObjectStore Collections C++ API Reference. Use the dictionary constructor only to create stack-based dictionaries, or dictionaries embedded within other objects. As an alternative to embedded dictionaries, consider using a reference or pointer, which allow you to use os_Dictionary::create.
os_Dictionary()
In general, it is preferable to use os_Dictionary::create(), as it results in higher performance and better locality of reference because of its underlying optimization for mutation.
Key type parameter
Dictionaries can have different types of keys as the key type parameters.
For integer keys, specify one of the following as key type:
void* keys
Use the type void* for pointer keys other than char* keys.
For char[] keys, use the parameterized type os_char_array<S>, where the actual parameter is an integer literal indicating the size of the array in bytes.
Destroying Collections
Each collection class has a static member for deleting a specified collection.
static void os_Collection::destroy(os_Collection<E>&) ; static void os_Set::destroy(os_Set<E>&) ; static void os_Bag::destroy(os_Bag<E>&) ; static void os_List::destroy(os_List<E>&) ; static void os_Array::destroy(os_Array<E>&) ; static void os_Dictionary::destroy(os_Dictionary<K, E>&) ;Destroying a collection class does not destroy the elements in the class; it deletes the specified collection itself and deallocates its associated storage. You can either call destroy() or simply delete the collection with the delete pointer.
All the collection class destroy() functions are described in Chapter 2, Collection, Query, and Index Classes, of the ObjectStore Collections C++ API Reference.
Inserting Collection Elements
You update collections by inserting and removing elements, or by using the assignment operators (see Copying, Combining, and Comparing Collections). The insert operations for os_Collection and its subtypes are declared this way:
void insert(const E) ;
Inserting Dictionary Elements
For dictionaries, you specify an entry, that is, a key, along with the element to be inserted. So os_Dictionary::insert() is declared this way:
void insert(const K &key, const E element) ; void insert(const K *key_ptr, const E element) ;These two overloadings are provided for convenience, so you can pass either the key or a pointer to the key.
os_database *db1 ; message *a_msg ; os_Set<message*> &temp_set = os_Set<message*>::create(db1) ; . . . temp_set.insert(a_msg) ; temp_set.insert(a_msg) ; /* set is unchanged */For collections with signal_duplicates behavior, inserting a duplicate raises err_coll_duplicate_insertion.
For collections with allow_duplicates behavior, each insertion increases the collection's size by one and increases by one the count (or number of occurrences) of the inserted element in the collection. You can retrieve the count of a given element with count(). Iteration with an unrestricted cursor visits each occurrence of each element.
Duplicate Keys
For dictionaries with signal_dup_keys behavior, if an attempt is made to insert something with the same key as an element already present, err_index_duplicate_key is signaled. Changing a Collection's Behavior
You can change many of a collection's behavioral characteristics. See Customizing Collection Behavior in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.
Changing a Collection's Representation Policy
You can change a collection's representation policy at any time, but keep in mind that changing a representation essentially reallocates a new object and copies the elements from the old representation to the new representation. See Customizing Collection Representation in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.
Removing Collection Elements with remove()
The remove operations for os_Collection and its subtypes are declared this way:
os_int32 remove(E) ;If you remove an element from a collection, the cardinality decreases by one, and the count of the element in the collection decreases by one. If you remove something that is not an element, the collection is unchanged.
os_database *db1 ; message *a_msg; . . . os_Set<message*> &temp_set = os_Set<message*>::create(db1) ; . . . temp_set.remove(a_msg) ; temp_set.remove(a_msg) ; /* set is unchanged */
void remove(const K &key, const E element) ; void remove(const K *key_ptr, const E element) ;If there is no entry with the specified key and element, the collection is unchanged. As with remove(), if you remove an entry from a dictionary, the cardinality decreases by one, and the count of the element in the collection decreases by one.
With dictionaries, you can also remove a specified number of entries with a specified key with these functions:
E remove_value(const K &key, os_unsigned_int32 n = 1) ; E remove_value(const K *key_ptr, os_unsigned_int32 n = 1) ;If there are fewer than n entries with the specified key, all entries in the dictionary with that key are removed. If there is no such entry, the dictionary remains unchanged.
Testing Collection Membership with contains()
To see if a given pointer is an element of a collection, use
os_int32 contains(E) const ;This function returns nonzero if the specified E is an element of the specified collection, and 0 otherwise.
os_boolean contains( const K const &key_ref, const E element ) const ; os_boolean contains( const K *key_ptr, const E element ) const ;With the first function you pass a reference to the key and with the second you pass a pointer to the key. Other than that, these two functions are equivalent. If there is no entry with the specified key and element, 0 (false) is returned.
os_int32 count(E e) ;If e is not an element of the collection, 0 is returned.
os_unsigned_int32 count_values(const K const &key_ref) const ; os_boolean contains(const K *key_ptr) const ;With the first function you pass a reference to the key and with the second you pass a pointer to the key. Other than that, these two functions are equivalent.
os_unsigned_int32 cardinality() const ;The cardinality of a collection that does not allow duplicates is the number of elements it contains. The cardinality of a collection that does allow duplicates is the sum of the number of occurrences of each of its elements.
os_int32 empty() ;This function returns true (a nonzero 32-bit integer) if it is empty, and false (0) otherwise.
Cursors
A cursor, an instance of os_Cursor, is used to designate a position within a collection. You can use cursors to traverse collections, as well as to retrieve, insert, remove, and replace elements.
Some members of the collection classes take cursor arguments. These functions support insertion, removal, and replacement of elements. See Accessing Collection Elements with a Cursor or Numerical Index.
Accessing Collection Elements with a Cursor or Numerical Index
Getting positional access within a collection
You can gain access to a specific place in a collection by means of a numerical index or a cursor as arguments to the following functions:
void os_Collection::insert_after(const E, const os_Cursor<E>&) void os_Collection::insert_after(const E, os_unsigned_int32) void os_Collection::insert_before(const E,The cursor-based overloadings must use a default vanilla cursor. The cursor-based overloadings of remove_at(), replace_at(), and retrieve() can also be used for unordered collections. (See Traversing Collections with Default Cursors for additional information.)
const os_Cursor<E>&) void os_Collection::insert_before(const E, os_unsigned_int32) void os_Collection::remove_at(const os_Cursor<E>&) void os_Collection::remove_at(os_unsigned_int32) E os_Collection::replace_at(const E, const os_Cursor<E>&) E os_Collection::replace_at(const E, os_unsigned_int32) E os_Collection::retrieve(const os_Cursor<E>&) const E os_Collection::retrieve(os_unsigned_int32) const
Manipulating first and last elements in a collection
There are also functions for inserting, removing, and retrieving elements from the beginning and the end of an ordered collection. These are declared as follows:
void os_Collection::insert_first(const E) void os_Collection::insert_last(const E) os_int32 os_Collection::remove_first(const E&) E os_Collection::remove_first() os_int32 os_Collection::remove_last(const E&) E os_Collection::remove_last()The integer-valued remove() and retrieve() functions return 0 if the collection had no elements to remove or retrieve (that is, was empty). Otherwise, they return a nonzero integer, and modify their arguments to indicate the removed or retrieved element.
If you perform any of these functions on an unordered collection created with the supertypes interface, the exception err_coll_not_supported is signaled. These operations will cause a compile-time error if performed on an unordered collection created with the subtypes interface. (Compile-time detection is possible because the unordered subtypes define the ordered operations as private.)
To traverse a collection, you create a cursor, an instance of the parameterized class os_Cursor, associated with the collection you want to traverse. The cursor records the state of an iteration by pointing to the element currently being visited. Each time through the loop, you advance the cursor to the next element and retrieve that element. Here is an example:
os_database *db1 ; . . . os_Collection<person*> &people = os_Collection<person*>::create(db1) ; . . . /* insertions into people */ os_Collection<person*> &teenagers = os_Collection<person*>::create(db1); person* p; os_Cursor<person*> c(people); for (p = c.first(); c.more() ; p = c.next()) if (p->age >=13 && p->age <= 19) teenagers.insert(p);The for loop in this example retrieves each element of the collection people and adds those between the ages of 13 and 19 to the collection teenagers.
os_Cursor(const os_Collection<E> &) ;Its constructor takes a Collection& (people in the example above) as argument. This is the collection to be traversed. The traversal proceeds in an arbitrary order for unordered collections and, for ordered collections, in the order in which the elements were inserted. See also Controlling Traversal Order, Performing Collection Updates During Traversal, and Restricting the Elements Visited in a Traversal in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide
Note that traversal of a collection with duplicates visits each element once for each time it occurs in the collection. For example, an element that occurs three times in a collection will be visited three times during a traversal of the collection.
os_Cursor::first()
The program then has a for loop. The traversal is performed with a for loop. The initialization part of the loop header is an assignment involving a call to the member function os_Cursor::first():
p = c.first()This positions the cursor at the collection's first element, and returns that element. If there is no first element, because the collection is empty, first() makes the cursor null and returns 0.
p = c.next()This positions the cursor at the collection's next element, and returns that element. If there is no next element, next() makes the cursor null and returns 0.
c.more()This function returns a nonzero 32-bit integer (true) when the cursor is still positioned at some element of the collection, and 0 (false) when it is null.
After next() is applied to the collection's last element, the cursor becomes null, and more() then returns false, terminating the loop.
os_database *db1 ; . . . os_Collection<person*> &people = os_Collection<person*>::create(db1) ; . . . /* inserts to people */ os_Collection<person*> &teenagers = os_Collection<person*>::create(db1) ; person* p ; os_Cursor<person*> c(people) ; for ( p = c.first() ; p ; p = c.next() ) if ( p->age >=13 && p->age <= 19 ) teenagers.insert(p) ;
void rebind(const os_Collection<E>&) ; void rebind(const os_Collection<E>&, _Rank_fcn) ;Once rebound, the cursor is positioned at the specified collection's first element.
This last overloading is for rebinding cursors whose order is specified by a rank function. See Path-Based Traversal in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.
Copying, Combining, and Comparing Collections
The class os_Collection defines several operators for assignment and comparison. Some of the assignment operators are related to the familiar set-theory operators union, intersection, and difference. In addition, some of the comparison operators are analogous to set-theory comparisons such as subset and superset. The collection operators are listed below. (LHS, below, stands for the operand on the left-hand side, and RHS stands for the right-hand side operand.) Collection comparison operators
So, for example, you can use the union equals operator, |=, as a convenient way of performing inserts:
os_Collection<message*> &a_set = os_Collection<message*>::create(db1) ; message *a_msg ; . . . a_set |= a_msg ;And you can use -= to remove elements:
a_set -= a_msg ;
big_list |= little_list ;is equivalent to iterating through little_list in the default order, performing an insert into big_list for each occurrence of each element of little_list. Assignment of one collection to another,
the_copy = the_original;is equivalent to first removing all the_copy's elements, and then iterating through the_original in default order, performing an insert into the_copy for each occurrence of each element of the_original.
In general, the update operators (=, |=, -=, &=) bundle together a sequence of inserts or removes of elements of one or more operands in the order in which those elements appear in the operands, the default iteration order for the operands. This describes only the behavior of the operators. The implementations might be different.
For example, to add all of a part's children to a given set, you might do
os_database *db1 ; . . . os_List<part*> &a_list = os_List<part*>::create(db1) ; part *a_part ; . . . a_list |= a_part->children ;This is behaviorally equivalent to
part *p ; os_Cursor<part*> c(a_part->children) ; for ( p = c.first() ; p ; p = c.next() ) a_list.insert(p) ;
OS_MARK_DICTIONARY( key-type, element-type)Put these calls in the schema source file. For example:
/* schema.cc */ #include <ostore/ostore.hh> #include <ostore/coll.hh> #include <ostore/coll/dict_pt.hh> #include <ostore/manschem.hh> #include "dnary.hh" OS_MARK_DICTIONARY(void*,Course*) ; OS_MARK_DICTIONARY(int,Employee**) ; OS_MARK_DICTIONARY(int,Course*) ; OS_MARK_SCHEMA_TYPE(Course) ; OS_MARK_SCHEMA_TYPE(Employee) ; OS_MARK_SCHEMA_TYPE(Department) ;For pointer keys, use void* as the key-type.
The OS_MARK_DICTIONARY() macro is described in ObjectStore Collections C++ API Reference.
Marking Transient Dictionaries
The OS_TRANSIENT_DICTIONARY() macro
If you use only transient dictionaries, you must call the macro OS_TRANSIENT_DICTIONARY() for each key-type/element-type pair that you use. If you use a particular instantiation of an os_Dictionary template both transiently and persistently, you should use the OS_MARK_DICTIONARY() macro only. The arguments for OS_TRANSIENT_DICTIONARY() are the same as for OS_MARK_DICTIONARY(), but you call OS_TRANSIENT_DICTIONARY() at file scope in an application source file, rather than in a schema source file.
OS_TRANSIENT_DICTIONARY(int,void*); OS _TRANSIENT_DICTIONARY(int,foo*);The problem is that both invocations of OS_TRANSIENT_DICTIONARY() will cause a stub routine to be defined for the key type int. Instead, you should only invoke OS_TRANSIENT_DICTIONARY() once for each key type, and use the macro OS_TRANSIENT_DICTIONARY_NOKEY() for each consecutive dictionary with the same key type. The correct use, given the example above, would be
OS_TRANSIENT_DICTIONARY(int,void*); OS _TRANSIENT_DICTIONARY_NOKEY(int,foo*);For related information on these macros, se the ObjectStore Collections C++ API Reference.
Dictionary Representation
Unlike the create operations for other collection classes, there are no arguments relating to representation. This is because you cannot directly control the representation for dictionaries. You can, however, use the class os_rDictionary instead of os_Dictionary. os_rDictionary is just like os_Dictionary, except that it records its elements using references (as do os_vdyn_hash and os_vdyn_bag), which eliminates address space reservation and can reduce relocation overhead.
Visiting the Elements with Specified Keys
For dictionaries, you can specify a restriction that is satisfied by elements whose key satisfies a specified range.
os_Cursor<E> ( const os_dictionary & coll, const os_coll_range &range, os_int32 options = os_cursor::unsafe ) ;An element satisfies this cursor's restriction if its key satisfies range. If the dictionary's key type is a class, you must supply rank and hash functions for the class. To do this, the dictionary must be created with the behavior maintain_key_order. See Customizing Collection Behavior in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.
Picking the Element with a Specified Key
For dictionaries, you can retrieve an element with the specified key with one of the following two functions:
E pick(const K const &key_ref) const ; E pick(const K *key_ptr) const ;These two differ only in that with one you supply a reference to the key, and with the other you supply a pointer to the key. Again, if there is more than one element with the key, an arbitrary one is picked and returned. If there is no such element and the dictionary has pick_from_empty_returns_null behavior, 0 is returned. If there is no such element and the dictionary does not have pick_from_empty_returns_null behavior, err_coll_empty is signaled.
E pick(const os_coll_range&) const ;For example:
a_dictionary.pick( os_coll_range(GE, 100) )returns an element of a_dictionary whose key is greater than or equal to 100. The dictionary must have the behavior os_dictionary::maintain_key_order for a pick() using an os_coll_range.
As with the other pick() overloadings, if there is more than one such element, an arbitrary one is picked and returned. If there is no such element and the collection has pick_from_empty_returns_null behavior, 0 is returned. If there is no such element and the dictionary does not have pick_from_empty_returns_null behavior, err_coll_empty is signaled.
a_dictionary.pick("Smith")returns an element of a_dictionary whose key is the string "Smith" (that is, whose key, k, is such that strcmp(k, "Smith") is 0).
When a key is removed, the destructor for the object of type K is run. Because the slot can then be reused, it is necessary for the destructor for the object of type K to null out any pointers to memory that get freed in the destructor.
Here is an example where type K is class myString:
class myString { private: char* theString; int len; } RMString::RMString(char* theChars) { if (theChars == 0) len = 0; else len = strlen(theChars); theString = new(os_segment::of(this), os_typespec::get_ char(),len + 1) char[len+1]; if (theChars == 0) theString[0] = `\0'; else strcpy(theString, theChars); } RMString::~RMString() { delete[] theString; /****************************************************** The following line solves the multiple delete problem ********************************************************/ theString = 0; }Failure to include the line theString = 0; results in the following error if a slot is reused:
Invalid argument to operator delete
<err-0025-0608>Delete failed. Cannot locate a persistent object at address 0x5780114 (err_invalid_deletion)
Each Student object contains two dictionaries that serve to associate a course with the grade the student got in the course. One dictionary supports lookup of the grade given the course, and the other supports lookup of the courses with a given grade.
Note that the dnary.cc example includes <ostore/coll/dict_pt.cc> instead of dict_pt.hh, and is needed since it contains the bodies of the functions declared in dict_pt.hh. You do not need to also include dict_pt.hh because it is included in dict_pt.cc. Finally, there is the file schema.cc, a schema source file.
Below is the file dnary.hh, which contains the class definitions. After that is the file dnary.cc, which contains the member function implementations.
/* dnary.hh */ #include <ostore/ostore.hh> #include <ostore/coll.hh> #include <ostore/coll/dict_pt.hh> #include <iostream.h> #include <stdlib.h> class Student ; class Grade ; class Course ; class Student { public: int get_id() const ; const os_Collection<Course*> &get_courses() const ; int add_course( Course*, Grade* = 0 ) ; void remove_course(Course*) ; Grade *get_grade_for_course(const Course*) const ; void set_grade_for_course(Course*, Grade*) ; os_Collection<Course*> &get_courses_with_grade( const Grade* ) const ; float get_gpa() const ; static os_typespec *get_os_typespec() ; Student(int id, os_segment*); ~Student() ; private: int id ; os_Collection<Course*> & courses ; os_Dictionary<const void*, Grade* & course_grade ; os_Dictionary<void*, Course* & grade_course ; } ; class Grade { public: const char *get_name() const ; float get_value() const ; static os_typespec *get_os_typespec() ; Grade(const char *name, float value, os_segment*) ; ~Grade() ; private: char *name ; float value ; } ; class Course { public: int get_id() const ; void set_id(int) ; static os_typespec *get_os_typespec() ; private: int id ; } ;
/* dnary.cc */ #include <ostore/coll/dict_pt.cc> typedef os_Dictionary<void*,Course*> grade_course_dnary ; typedef os_Dictionary<void*,Grade*> course_grade_dnary ; /* Student member function implementations */ int Student::get_id() const { return id ; } const os_Collection<Course*> &Student::get_courses() const { return courses ; } int Student::add_course( Course *c, Grade *g ) { if ( courses.contains(c) ) return 0 ; courses.insert(c) ; if (g) { grade_course.insert(g, c) ; course_grade.insert(c, g) ; } /* end if */ return 1 ; } void Student::remove_course(Course *c) { courses.remove(c) ; grade_course.remove( course_grade.pick(c), c ) ; course_grade.remove_value(c) ; } Grade *Student::get_grade_for_course(const Course *c) const { return course_grade.pick(c) ; } void Student::set_grade_for_course(Course *c, Grade *g) { grade_course.remove(course_grade.pick(c), c) ; course_grade.remove_value(c) ; grade_course.insert(g, c) ; course_grade.insert(c, g) ; } os_Collection<Course*> & Student::get_courses_with_grade(const Grade *g) const { os_Collection<Course*> &the_courses = os_Collection<Course*>::create( os_database::get_transient_database() ) ; os_cursor cur(grade_course, os_coll_range( os_collection::EQ, g)) ; for ( Course *c = (Course*) cur.first() ; c ; c = (Course*) cur.next() ) the_courses.insert(c) ; return the_courses ; } float Student::get_gpa() const { float sum = 0.0 ; os_cursor c(course_grade) ; for ( Grade *g = (Grade*) c.first(); g; g = (Grade*) c.next() ) sum = sum + g->get_value() ; return sum / course_grade.size() ; } Student::Student(int i, os_segment *seg) : courses(os_Collection<Course*>::create(seg)), course_grade( os_Dictionary<const void*, Grade*>::create(seg, 10,os_collection::pick_from_empty_returns_null) ), grade_course(os_Dictionary<void*, Course*>::create(seg)) { id = i; } Student::~Student() { os_Collection<Course*> *courses_ptr = &courses ; os_Dictionary<const void*, Grade*> *course_grade_ptr = &course_grade ; os_Dictionary<void*, Course*> *grade_course_ptr = &grade_course ; delete courses_ptr ; delete course_grade_ptr ; delete grade_course_ptr ; } /* Grade member function implementations */ const char *Grade::get_name() const { return name ; } float Grade::get_value() const { return value ; } Grade::Grade(const char *n, float v, os_segment *seg) { name = new( seg, os_typespec::get_char(), strlen(n)+1 ) char[strlen(n)+1] ; strcpy(name, n) ; value = v ; } Grade::~Grade() { delete name ; } /* Course member function implementations */ int Course::get_id() const { return id ; } void Course::set_id(int i) { id = i ; }
/* schema.cc */ #include <ostore/ostore.hh> #include <ostore/coll.hh> #include <ostore/coll/dict_pt.hh> #include <ostore/manschem.hh> #include "dnary.hh" void dummy () { OS_MARK_DICTIONARY(void*,Course*) ; OS_MARK_DICTIONARY(void*,Grade*) ; OS_MARK_SCHEMA_TYPE(Course) ; OS_MARK_SCHEMA_TYPE(Student) ; OS_MARK_SCHEMA_TYPE(Grade) ; }
The data member Student::course_grade contains a reference to a dictionary that maps each course to the grade the student got for that course. This dictionary supports lookup of the grade given the course.
The data member Person::grade_course contains a reference to a dictionary that maps each grade to the courses for which the student got that grade. This dictionary supports lookup of the courses given the grade.
The function Student::add_course() first checks to see if the specified course has already been added. If it has, 0 (indicating failure) is returned. If it has not, the function inserts the specified course into the collection referred to by Student::courses. Then, if a grade is specified, entries are inserted into the dictionaries referred to by Student::course_grade and Student::grade_course. Finally, 1 (indicating success) is returned.
The function Student::remove_course() removes the specified course from Student::courses. If the course is not an element of courses, the call to remove() has no effect.
Then, using pick() on course_grade, remove_course() determines the grade for the given course. The grade and the course are then passed to os_Dictionary::remove() to remove from Student::grade_course the entry whose value is the given course. The function add_course() ensures that there is at most one.
If the course is not an element of the dictionary, pick() returns 0, and the call to remove() has no effect. Note that pick() returns 0 in this case instead of raising an exception because the dictionary (course_grade) was created with pick_from_empty_returns_null behavior.
Finally, using os_Dictionary::remove_value(), remove_course() removes from Student::Course_grade the entry whose key is the given course. Again, add_course() ensures there is at most one. If the dictionary has no entry whose key is that course, the call to remove_value() has no effect.
Student::get_grade_for_course() uses os_Dictionary::pick() to retrieve from Student::course_grade the element whose key is the given course.
Student::set_grade_for_course() first takes precautions in case the specified course already has been assigned a grade. It removes from Student::course_grade and Student::grade_course any entries with the given course. It does this as follows.
First, the function performs remove() on grade_course, passing in the grade for the given course (determined by performing pick() on course_grade), and also passing in the given course itself. If no grade has been set for the course, pick() returns 0, and the call to remove() has no effect. Then the function uses remove_value() to remove from course_grade the entry, if there is one, whose key is the given course.
Next, Student::set_grade_for_course() inserts into grade_course an entry whose key is the specified grade and whose value is the specified course. Finally, it inserts into course_grade an entry whose key is the specified course and whose value is the specified grade.
The function Student::get_courses_with_grade() returns a reference to a collection of the courses for which the student got the specified grade. It creates a collection on the transient heap, and then uses a restricted cursor to visit each element of grade_course whose key is the specified grade. As each qualifying element is visited, it is inserted into the newly created collection. Finally, a reference to the collection is returned.
The function Student::get_gpa() returns the student's grade point average. It visits each element of the dictionary course_grade, summing the result of performing get_value() on each element along the way. When the traversal is complete, the sum is divided by the dictionary's size to get the average, which is returned.
The Student constructor allocates an os_Collection and two instances of os_Dictionary in the specified segment. Note that the dictionary course_grade is created with pick_from_empty_returns_null behavior.
Updated: 03/31/98 17:00:02