To define an operations class, use the predefined templates for standard functions, and define the specific functions individually.
Consider, for example, a person's name (PersonName) and phone number (TNumber). The name serves as the key for an address list, while the phone number serves as the key for a phone list. Because the key function is already defined to yield the person's name, the phone list has to be instantiated in the following way:
This is the header file:
//person.h - header file containing class Person #include <iostream.h> #include <istring.hpp> #include <istdops.h> class Person { IString PersonName; IString TNumber; public: //constructor Person() : PersonName(""), TNumber("") { } //copy constructor Person(IString name, IString number) : PersonName(name), TNumber(number) { } IString const& GetPersonName() const { return PersonName; } IString const& GetTNumber() const { return TNumber; } IBoolean operator==(Person const& A) const { return (PersonName == A.GetPersonName()) && (TNumber==A.GetTNumber()); } IBoolean operator<(Person const& A) const { return (PersonName < A.GetPersonName()); } }; ostream& operator<<(ostream& os, Person A); class PhoneOps : public IStdMemOps, public IStdAsOps<Person> { public: IString const& key(Person const& A) const { return A.GetTNumber(); } IStdCmpOps <IString> keyOps; }; inline IString const& key(Person const& A) //Key access { return A.GetPersonName(); }
This is the main file:
//main.cpp - main file #include "person.h" //person.h from the previous example #include <istdops.h> #include <ikeyset.h> typedef IKeySet <Person,IString> AddressList; typedef IGKeySet <Person,IString,PhoneOps> PhoneList; ostream& operator<<(ostream& os,Person A) { return (os << endl << A.GetPersonName() << " " << A.GetTNumber()); } void main() { AddressList Business; PhoneList PhoneBook; AddressList::Cursor myCursor1(Business); PhoneList::Cursor myCursor2(PhoneBook); Person A("Peter Black","714-50706"); Person B("Carl Render","714-540321"); Person C("Sandra Summers","x"); Person D("Mike Summers","x"); Business.add(A); Business.add(B); Business.add(C); Business.add(D); PhoneBook.add(A); PhoneBook.add(B); PhoneBook.add(C); PhoneBook.add(D); cout << "\n\nPhoneBook before removing an element: "; forICursor(myCursor2) { cout<<PhoneBook.elementAt(myCursor2); } cout << "\n\nPhoneBook after removing an element: "; PhoneBook.removeElementWithKey("714-50706"); forICursor(myCursor2) { cout<<PhoneBook.elementAt(myCursor2); } cout << "\n\nBusiness before removing an element: "; forICursor(myCursor1) { cout<<Business.elementAt(myCursor1); } cout << "\n\nBusiness after removing an element: "; Business.removeElementWithKey("Peter Black"); forICursor(myCursor1) { cout<<Business.elementAt(myCursor1); } }
The functions that are required for a particular Collection Class depend not only on the abstract class but also on the concrete implementation choice. If you choose a set to be implemented through a hash table, the elements require a hash function. If you choose a (sorted) AVL tree implementation, elements need a comparison function. Even the default implementations may require more functions to be provided than would be necessary for the collection interface.
Introduction
to the Collection Classes
Collection Characteristics
Access
by Key
Equality
Relation
Uniqueness of Entries
Overview
of Iteration
Iteration
with Cursors
Iteration with allElementsDo
Flat
Collections
Trees
Defining Equality
Relation
Defining Key or Element
Equality
Implementing
Element- and Key-Type Functionality
Defining
Member Functions of the Element Object Type
Defining
Separate Global Functions
Using or
Defining an Element Operation Class
Memory Management
with Element Operation Classes
Things to Watch
Out For
Choosing the
Appropriate Smart Pointer Class
Constructing Smart
Pointers
Using Element
Pointers
Using Cursors to Locate
and Access Elements
Instantiating the
Collection Classes