ObjectStore C++ API User Guide

Chapter 5

Collections

The information about collections is organized in the following manner:

Collections Overview

A collection is an object whose purpose is to group together other objects. It provides a convenient means of storing and manipulating groups of objects, supporting operations for inserting, removing, and retrieving elements.

In order to implement collection functionality, the ObjectStore collection facility provides

Collection Class Library

ObjectStore provides a library of collection classes. These classes provide the data structures for representing such collections, encapsulated by member functions that support various forms of collection manipulation, such as element insertion and removal. Retrieval of a given collection's elements for examination or processing one at a time is supported through the use of a cursor class.

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.

Collection Query and Manipulation Features

Collections form the basis of the ObjectStore query facility, which allows you to select those elements of a collection that satisfy a specified condition. Queries with simple conditions are discussed in this chapter. Queries with complex conditions are described in Chapter 5, Queries and Indexes, of the ObjectStore Advanced C++ API User Guide.

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.

This allows you, for example, to create either ordered or unordered collections, and collections that either do or do not allow duplicates. You can also choose from among a group of system-supplied collection representations, such as hash tables and packed lists. You can even specify how a collection's representation is to change in response to changes in the collection's size.

Collections are commonly used to model many-valued attributes, and they can also be used as class extents (which hold all instances of a particular class). Collections of one type - dictionaries - associate a key with each element or group of elements, and so can be used to model binary associations or mappings. ObjectStore dictionaries are described in detail in Dictionaries.

Requirements for Applications Using Collections

Note the following requirements for using ObjectStore collections:

Include Files

Programs that use ObjectStore collections must include the header file <ostore/coll.hh> after including the standard ObjectStore header file <ostore/ostore.hh>.

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();

Linking

Programs that use ObjectStore collections must also link with the appropriate ObjectStore collections libraries and library schema. Collections library names are platform specific:
UNIX PlatformsWindows 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.

Note, though, that because the functionality is so rich and varied, the collections facilities add overhead to your code and database; if your application does not require collections, a simple linked list you can write yourself might be a more suitable choice.

Using the Example: Linked List of Notes as a base, the following example illustrates how to use collections.

Header file: note.hh
      #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;
      };
Main program: main.cc
      /* ++ 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();
      }

Choosing a Collection Type

This section contains a brief description of each type of ObjectStore collection, followed by a simple decision tree you can use to choose a collection type to suit your particular behavioral requirements.

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

os_Set and os_set

Sets, as with familiar data structures such as linked lists and arrays, have elements, objects that the set serves to group together. But, in contrast to lists and arrays, the elements of a set are unordered. You can use sets to group objects together when you do not need to record any particular order for the objects.

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_Bag and os_bag

Bags are collections that not only keep track of what their elements are, but also of the number of occurrences of each element. In other words, bags allow duplicate elements. The class os_Bag provides all the operations available for sets, as well as an operation, count(), that returns the number of occurrences of a given element in a given collection.

os_List and os_list

In addition to sets and bags, the ObjectStore collection facility supports lists, collections that associate a numerical position with each element based on insertion order. Lists can either allow or disallow duplicates (by default they allow duplicates). In addition to simple insert (insert into the beginning or end of the collection) and simple remove (removal of the first occurrence of a specified element) you can insert, remove, and retrieve elements based on a specified numerical position, or based on a specified cursor position (see Accessing Collection Elements with a Cursor or Numerical Index).

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.

The os_Collection class permits you to change these and other behaviors mentioned above for an os_Collection. This means that an instance of os_Collection could, at one point in its lifetime, have set-like behavior, and at another point have bag-like or list-like behavior.

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.

Dictionaries are somewhat different from other ObjectStore collection classes in their use of keys. See Dictionaries for additional information on how dictionaries differ from other kinds of ObjectStore collections.

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.
Collections ClassMaintain Element OrderAllow DuplicatesSignal DuplicatesAllow Nulls
os_SetForbiddenForbiddenOff by defaultOff by default
os_BagForbiddenRequiredForbiddenOff by default
os_ListRequiredOn by defaultOff by defaultOff by default
os_CollectionOff by defaultOff by defaultOff by defaultOff by default
os_ArrayRequiredOn by defaultOff by defaultRequired
os_DictionaryForbiddenRequiredForbiddenRequired

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.
Collections ClassSize 0 to 20Greater Than 20
os_Setos_chained_listos_dyn_hash
os_Bagos_chained_listos_dyn_bag
os_Listos_chained_listos_packed_list
os_Collectionos_chained_listos_dyn_hash
os_Arrayos_chained_listos_packed_list

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.
Collections Class Size 0 to 20Greater Than 20 (Do Not Maintain Cursors)Greater Than 20 (Maintain Cursors)
os_Set os_chained_list os_dyn_hash Not applicable
os_Bag os_chained_list os_dyn_bag Not applicable
os_List os_chained_list os_packed_list os_packed_list
os_Collection os_chained_list os_dyn_hash os_packed_list
os_Array os_chained_list os_packed_list os_packed_list

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.)

Example: os_Set
The example below uses an instance of os_Set, one of the classes supplied by the collection facility. This class defines a part that includes the part number and the responsible engineer.

      #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;
                        }
      };

Using Collections Without Parameterization

You can choose to use the following nonparameterized collection classes.

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.

Difference between parameterized and nonparameterized interfaces
The difference between the parameterized and nonparameterized interfaces is that with the parameterized interface you can inform the compiler of the type of element a collection is to have, allowing the compiler to provide an extra measure of type safety. With the nonparameterized interface, elements are always typed as void* pointers.

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:

Example: os_set()
      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.

Nonparameterized collections are typed void*
When you use nonparameterized collections, elements are typed void*. This means you must cast the result of retrieving a collection element, for example when using pick() or traversing a collection using a cursor.

Creating Collections

This section presents information on creating collections. The os_Array, os_Bag, os_Collection, os_List, and os_Set classes are discussed together in the first subsection. os_Dictionary is discussed separately in the following subsection.

General Guidelines

You can create collections for each collections class with the following functions:
Collection TypeCreate FunctionConstructor Function
Arrayos_Array::create() os_Array::os_Array()
Bagos_Bag::create() os_Bag::os_Bag()
Collectionos_Collection::create() os_Collection::os_Collection()
Listos_List::create() os_List::os_List()
Set os_Set::create() os_Set::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.

All other arguments are optional, because they are declared with default values.

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 TypeParameterized ClassNonparameterized Class
Arrayos_Arrayos_array
Bagos_Bagos_bag
Collectionos_Collectionos_collection
Listos_Listos_list
Set os_Setos_set

To find out how to

Creating Dictionaries

Dictionaries are unordered collections that allow duplicates. Unlike other collections, dictionaries associate a key with each element in the collection. The key can be a value of any C++ fundamental type, user-defined type, or 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.

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.

os_Dictionary::
create()
There are four overloadings of os_Dictionary::create(), as described in the ObjectStore Collections C++ API Reference. The first argument, which specifies where to allocate the new collection, is the only argument required for os_Dictionary::create(). Depending on the overloading, it specifies a database, segment, or object cluster. All other arguments are optional, since they are declared with default values.

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::
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.

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.

Integer keys

For integer keys, specify one of the following as key type:

For class keys, the class must have a destructor that zeroes any pointers it contains, a no-arg constructor, and operator=.

void* keys

Use the type void* for pointer keys other than char* keys.

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.

If a dictionary's key type is char*, the dictionary makes its own copies of the character array upon insert. If the dictionary does not allow duplicate keys, you can significantly improve performance by using the type os_char_star_nocopy as the key type. With this key type the dictionary copies the pointer to the array and not the array itself. You can freely pass chars to this type.

Note that you should not use os_char_star_nocopy with dictionaries that allow duplicate keys.

char[], char*, and os_char_star_nocopy all use strcmp for comparison.

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) ;
For more information on behavioral enumerators
Refer to Customizing Collection Behavior in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide for descriptions of the behavior enumerators (such as signal_dup_keys, allow_duplicates, and signal_duplicates) that are mentioned in this section.

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.

Caution
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_am_dup_key is signaled.

Duplicate Insertions

For collections without allow_duplicates and signal_duplicates behavior, inserting something that is already an element of the collection leaves the collection unchanged.

      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.

Null Insertions

If you insert a null pointer (0) into a collection without allow_nulls behavior, the exception err_coll_nulls is signaled.

Ordered Collections

Inserting into a collection with maintain_order behavior adds the element to the end of the collection. See also Accessing Collection Elements with a Cursor or Numerical Index.

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 */

Ordered Collections

For collections with maintain_order behavior, remove() removes the specified element from the end of the collection. There are also overloadings of remove() that allow you to remove at a numerical index value in the collection or remove at the position of the cursor.

Removing Dictionary Elements

For dictionaries, you can also remove the entry with a specified key and element with remove_value(). This function is faster than remove(), so if you can specify the key, use remove_value() instead of remove(). There are two overloadings that differ only in that you pass a pointer to the key in one overloading and you pass a reference to the key in the other overloading.

      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.

Caution regarding duplicate insertions
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_am_dup_key is signaled. Refer to Customizing Collection Behavior in Chapter 4 of the ObjectStore Advanced C++ API User Guide for information about signal_dup_keys.

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.

Dictionaries

For dictionaries, you can determine if there is an entry with a given key and element.

      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.

Finding the Count of an Element with count()

To find the count of a given element in a collection, use

      os_int32 count(E e) ;
If e is not an element of the collection, 0 is returned.

Dictionaries

For dictionaries, you can determine the number of entries with a specified key with one of these functions:

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.

Finding the Size of a Collection with cardinality()

You can determine the number of elements in a collection with the member function os_collection::cardinality().

      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.

Checking for an Empty Collection with empty()

You can test to see if a collection is empty with the member function empty().

      os_int32 empty() ;
This function returns true (a nonzero 32-bit integer) if it is empty, and false (0) otherwise.

Using Cursors for Navigation

The ObjectStore collection facility provides a number of classes that help you navigate within a collection. The os_Cursor class, the os_index_path class, and the os_coll_range class all help you insert and remove elements, as well as retrieve particular elements or sequences of elements.

The os_Cursor class is discussed here and in the immediately following sections of this chapter. See also os_Cursor in the ObjectStore Collections C++ API Reference.

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.

When you create a vanilla cursor with no index path or collection range, you specify its associated collection, and the cursor is positioned at the collection's first element. With members of os_Cursor, you can reposition the cursor, as well as retrieve the element at which the cursor is currently positioned. See Traversing Collections with Default Cursors.

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, 
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
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.)

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.)

Traversing Collections with Default Cursors

The ObjectStore collection facility allows you to program loops that process the elements of a collection one at a time. When you use it, you do not need to know how many elements are in the collection; each time through the loop you can, in effect, test whether more elements remain to be visited. So you do not need to worry about loop bounds.

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:

Example: iterating through a collection with os_Cursor
      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.

Creating Default Cursors

The class os_Cursor is a parameterized class supplied by the ObjectStore class library.

      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's parameter (person* in the example) indicates the type of elements in the collection being traversed. So the cursor's parameter must be the element type (see Using Collections with the Element Type Parameter) of the collection passed as the constructor argument.

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.

os_Cursor::next()

The incrementation part of the for loop header is an assignment involving a call to the member function os_Cursor::next():

      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.

os_Cursor::more()

The loop's condition is a call to the member function os_Cursor::more():

      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.

Alternative to using more()
For collections that do not allow null elements, you can take advantage of the fact that first() and next() return null pointers when there is no first or next element. This means you can use the values returned by these functions (in this case p) as the loop condition (as long as the collection contains no null pointers - see Customizing Collection Behavior in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide).

      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) ;

Rebinding Cursors to Another Collection

You can change a cursor's associated collection with the following members of os_Cursor:

      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

Dual Purpose of the Operators

All these operators have a dual purpose. They can be used on two collections, or they can be used on a collection (as left-hand operand) and an instance of that collection's element type (as right-hand operand). For example, they can be used on a set of parts and a part. In that case, the instance of the collection's element type is treated as a collection whose one and only element is that instance. Performance varies by representation.

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 ; 

Ordered Collections and Collections with Duplicates

When you use the update operators, such as |=, on ordered collections or collections that allow duplicates, the result can be understood in terms of performing an iteration on one or more of the operands. So, for example:

      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) ;

Dictionaries

Dictionaries are unordered collections that allow duplicates. Unlike other collections, dictionaries associate a key with each element. The key can be a value of any C++ fundamental type or 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.

Required include files
To use ObjectStore's dictionary facility, you must include the files <ostore/ostore.hh>, <ostore/coll.hh>, and <ostore/coll/dict_pt.cc> in this order. You must include dict_pt.cc when instantiating the template because it contains the bodies of the functions declared in ostore/coll/dict_pt.hh; however, users of the template can just include dict_pt.hh.

Marking Persistent Dictionaries

The OS_MARK_DICTIONARY() macro
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)
Put these calls in the schema source file. For example:

Example: schema file
      /* 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.

However, using OS_TRANSIENT_DICTIONARY() more than once with the same key type will result in a complication error. For example, the following will not compile correctly:

      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 Behavior

Every dictionary has the following properties:

By default a new dictionary also has the following properties:

You can customize the behavior of new dictionaries with regard to these last three properties. For large dictionaries that maintain key order, there is also an option for reducing contention. See Customizing Collection Behavior in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.

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.

In addition to the key type and element type parameters, the class os_rDictionary also has a reference type parameter, whose actuals are ObjectStore reference types. Specify either os_reference or os_reference_version.

The os_rDictionary class is described in the ObjectStore Collections C++ API Reference.

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.

Retrieving ranges of elements
For dictionaries, you can also retrieve an element whose key satisfies a specified collection range (see Specifying Collection Ranges in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide) with

      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.

Retrieving elements when the key type is a class
If the dictionary's key type is a class, you must supply rank and hash functions for the class. See Supplying Rank and Hash Functions in Chapter 4, Advanced Collections, of the ObjectStore Advanced C++ API User Guide.

The key types char*, char[ ], and os_char_star_nocopy are each treated as a class whose rank and hash functions are defined in terms of strcmp(). For example, for char*:

      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).

Writing Destructors for Dictionaries

There are circumstances in which a slot in an ObjectStore dictionary can be reused. A slot is used for the first time when the first item is hashed to that slot during an insert. A removal of that item will cause the slot to be emptied and marked as previously occupied. A subsequent insert of a key that hashes to that slot can result in the reuse of that slot to hold this new key.

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)

Example: Using Dictionaries

A ternary relationship is a relationship among three objects, like "student x got grade y in course z". Dictionaries are often useful in representing ternary relationships. This section contains an example involving the classes Student, Grade, and Course, which allow you to store and retrieve information about who got what grade in what course.

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.

Header file: dnary.hh
/* 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 ;
} ;
Main program: dnary.cc
/* 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 file: schema.cc
/* 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 example explained...
The data member Student::courses contains a reference to a collection of pointers to the courses the student has taken.

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.



[previous] [next]

Copyright © 1997 Object Design, Inc. All rights reserved.

Updated: 03/31/98 17:00:02