Searching for elements

Figure 1 shows a subset of the protocol used to search for specific elements of a collection.


Member

Member can test to see if a given object is found inside a collection. This operation is more subtle than it might appear as subsequent example programs will demonstrate. The key issue is the way two objects are compared to determine if the object being sought matches any of the elements of the collection.

Likewise, After and Before depend on the comparison of objects. Member takes a reference to the type of element the collection is declared to hold--a TCollectibleLong in the example programs. Member returns a pointer to an object of the same type. If the object given to Member as an argument is found, Member returns a pointer to the first matched element of the collection. Otherwise it returns NIL. The return value can be used as a boolean in conditional expressions.

          TCollectibleLong *p = new TCollectibleLong(3);
          if (collection->Member(*p))
              printf("found");
          else
              printf("not found");

After and Before

After behaves just like Member, except that it returns a pointer to the element after the found object. Similarly Before returns a pointer to the element appearing in the collection in front of the object given as an argument to Before. Both After and Before return NIL if the object given as an argument is not found, or if there is no element after (for After) or before (for Before) it in the collection. In fact, Member, After, and Before have identical declarations inside
TArrayOf.

          template <class AType>
          class   TArrayOf: public TIndexedSequenceOf<AType>,
                            public TPrimitiveArray {
          public:
              ...
              virtual AType*  Member(const AType& obj) const;
              virtual AType*  After(const AType& obj) const;
              virtual AType*  Before(const AType& obj) const;
              ...
          }
Given a pointer to the first element in a collection,

        TCollectibleLong *pFirst = collection->First();
a pointer to the second element can be obtained using After:

        TCollectibleLong *p = collection->After(*pFirst);
Note that it is necessary to dereference the pointer pFirst because After expects a reference to a TCollectibleLong even though it returns a pointer. The following example programs shows the use of After to return the element after the first element in a collection and uses Before to return the element before the last element in the same collection.

Sample program using After, Before

      // Copyright (c) 1994 Taligent, Inc. All Rights Reserved.
      
      #ifndef Taligent_COLLECTIONS
      #include <Collections.h>
      #endif
      
      #ifndef _H_STDIO
      #include <stdio.h>
      #endif
      
      int main()
      {
          TArrayOf<TCollectibleLong>* collection =
              new TArrayOf<TCollectibleLong>(NIL, NIL);
      
          collection->Add(new TCollectibleLong(0));
          collection->Add(new TCollectibleLong(1));
          collection->Add(new TCollectibleLong(2));
          collection->Add(new TCollectibleLong(3));
      
          TCollectibleLong *p, *pFirst, *pLast;
      
          pFirst = collection->First();
          p = collection->After(*pFirst);
          printf("First=>%d\n", pFirst->GetValue());
          printf("After First=>%d\n", p->GetValue());
      
          pLast = collection->Last();
          p = collection->Before(*pLast);
          printf("Before Last=>%d\n", p->GetValue());
          printf("Last=>%d\n", pLast->GetValue());
      
          collection->DeleteAll();
          delete collection;
          return 0;
      }
Here's the resulting output.

          First=>0
          After First=>1
          Before Last=>2
          Last=>3

Member

Member works in much the same way. However, using Member can lead to some interesting results. For example, it's possible that two TCollectibleLongs with identical internal values are not considered to be equal when compared. As an example consider the following program.

Sample program using Member

      // Copyright (c) 1994 Taligent, Inc. All Rights Reserved.
      
      #ifndef Taligent_COLLECTIONS
      #include <Collections.h>
      #endif
      
      #ifndef _H_STDIO
      #include <stdio.h>
      #endif
      
      int main()
      {
          TArrayOf<TCollectibleLong>* collection =
              new TArrayOf<TCollectibleLong>(NIL, NIL);
      
          collection->Add(new TCollectibleLong(0));
          collection->Add(new TCollectibleLong(1));
          collection->Add(new TCollectibleLong(2));
          collection->Add(new TCollectibleLong(3));
      
          TCollectibleLong *p;
      
          p = collection->First();
          printf("Member First? =>%s\n", (collection->Member(*p) ? "yes" : "no"));
      
          p = new TCollectibleLong(4);
          printf("Member 4? =>%s\n", (collection->Member(*p) ? "yes" : "no"));
          delete p;
      
          p = new TCollectibleLong(3);
          printf("Member 3? =>%s\n", (collection->Member(*p) ? "yes" : "no"));
          delete p;
      
          collection->DeleteAll();
          delete collection;
          return 0;
      }
The resulting output reveals some important and possibly surprising results. After the four TCollectibleLongs are added to collection three tests are made for membership of three different components. A pointer, p is first declared and then set to point to the first member of the collection.

          TCollectibleLong *p;
          p = collection->First();
The test for membership is embedded in the following printf function call.

          printf("Member First? =>%s\n",
                  (collection->Member(*p) ? "yes" : "no"));
The subexpression containing the actual test for membership is a tertiary ?: operator which returns the string "yes" if p is a member of collection and "no" if it is not.

        (collection->Member(*p) ? "yes" : "no")
Not surprisingly the result of this first test shows that the element returned by collection->First() is a member of collection.

        Member First? =>yes
In the second test, p is set to a new instance of TCollectibleLong with a value of 4.

          p = new TCollectibleLong(4);
          printf("Member 4? =>%s\n",
                  (collection->Member(*p) ? "yes" : "no"));
A a printf call similar to the first test produces the output

        Member 4? =>no
Again, this is no surprise because none of the TCollectibleLongs in collection has a value of 4.

The final test, however, might surprise you. The pointer p is set to a new instance of TCollectibleLong with a value of 3.

          p = new TCollectibleLong(3);
          printf("Member 3? =>%s\n", (collection->Member(*p) ? "yes" : "no"));
The resulting output is

        Member 3? =>no

Comparing collection elements for equality

You may have expected Member to return a true value in this case because collection does contain a TCollectibleLong with a value of 3. The problem is the way that comparison is conducted when testing to see if an element is a member of a collection. By default, comparison of elements is based on address pointers. For two objects to compare as equal, they must occupy the same location in memory. Because the second TCollectibleLong(3) was created with a new operation, new heap memory was allocated for it and p points to it. Therefore p does not have the same value as the pointer returned by collection->Last().

Depending on what you are trying to do, this definition of equality (defined as sameness) might be too restrictive. You might simply want use Member to find out if a collection contains an instance of TCollectibleLong that has a value of 3. What you are bumping up against here is the mathematical distinction between identity and equality. Identity implies that two objects under consideration are in fact the same object. Equality is less specific in nature. Equality implies that two objects under consideration may be interchanged for a given operation and still produce the same result. Either of two instances of the integer 3 will produce the same result when added to the integer 2. This is true even if both instances of the integer reside at different machine addresses.

Implied in this definition of equality is that the meaning of equality depends on context; the meaning of equality depends on how two items being compared are intended to be used. If you are doing pointer arithmetic based on object references, you may need to define equality and identity as the same thing. If, on the other hand, you are searching a word processor document (a collection or sequence of strings) with a search and replace command, equality of two strings will be true if each of the character elements of two different strings match.

For this reason all MCollectible objects define member functions for determining identity (IsSame) and equality (IsEqual). These member functions are shown in Figure 1.


IsSame and IsEqual

The IsSame member function is almost always based on pointer comparison as was the operation invoked by Member in the previous program. Most classes, however, will want to override the definition of IsEqual if it makes sense to define equality in a more general way. TCollectibleLong overrides IsEqual to define equality in terms of the values inside of TCollectibleLong objects instead of by the addresses they occupy in memory.

It's useful to look at TCollectibleLong::IsEqual as you might have to override IsEqual if you create a subclass of MCollectible which you plan to use with collections.

      bool TCollectibleLong::IsEqual(const MCollectible* obj) const
      {
          return (obj != NIL)
              ? (fvalue == ((TCollectibleLong*)obj)->GetValue()) : true;
      }
This code defines how to TCollectibleLong objects should be compared for equality. If the pointer to the second object, obj, is not NIL, the code casts obj as a pointer to TCollectibleLong and retrieves its value though TCollectibleLong::GetValue. If the long returned by GetValue is equal to the internal value of the first object, the result is true and the objects are considered equal.

Compare this to the code for IsSame (identity) which TCollectibleLong inherits from MCollectible.

      bool MCollectible::IsSame(const MCollectible* obj) const
      {
          return (obj == this);
      }
As you can see, identity is based on the idea that two objects are identical if their pointers are equal.

The question remains, if TCollectibleLong defines equality based value rather than address, why did the Member function in the previous example program not match the two TCollectibleLongs which both had the value 3? The reason has to do with the way the collection was declared. Recall that the constructor for collections requires two arguments.

          TArrayOf<TCollectibleLong>* collection =
              new TArrayOf<TCollectibleLong>(NIL, NIL);

Defining equality with comparators

The first argument is a comparator and the second argument is a streamer which the collection can use to save itself as a persistent object. So far all the examples have specified NIL for both comparators and streamers used by collections. A NIL comparator tells the collection to base comparison on IsSame for its elements rather than on definition specified by the programmer. If you want to base comparison on IsEqual, you need to create a comparator object and give that object to the constructor.

Fortunately comparators are templatized in a way that coincides with collections. If the class of elements you are managing in a collection implements the proper behavior for IsEqual, you can easily create a comparator without having to define a new class. A comparator based on IsEqual for TCollectibleLongs can be created as follows.

          TMCollectibleComparator<TCollectibleLong> *comparator =
              new TMCollectibleComparator<TCollectibleLong>;
The resulting pointer can now be given to the constructor for a collection.

          TArrayOf<TCollectibleLong>* collection =
              new TArrayOf<TCollectibleLong>(comparator, NIL);
NOTE The comparator is specified in the constructor is adopted by the collection.

That's all there is to it. With these two changes, the previous program will now return a true value for the expression

        collection->Member(new TCollectibleLong(3))
even though the two TCollectibleLongs being compared occupy two different locations in memory.

CAUTION Even though you create a comparator with new, you should not
delete it.

        delete comparator.   // error: don't do this.
Once you pass the comparator off to the collection's constructor, the collection adopts and owns it and will delete it automatically. Be sure you don't delete it yourself or you might get a core dump when you delete the collection.

The entire program follows.

Sample program using a comparator

      // Copyright (c) 1994 Taligent, Inc. All Rights Reserved.
      
      #ifndef Taligent_COLLECTIONS
      #include <Collections.h>
      #endif
      
      #ifndef _H_STDIO
      #include <stdio.h>
      #endif
      
      int main()
      {
          TMCollectibleComparator<TCollectibleLong> *comparator =
              new TMCollectibleComparator<TCollectibleLong>;
          TArrayOf<TCollectibleLong>* collection =
              new TArrayOf<TCollectibleLong>(comparator, NIL);
      
          collection->Add(new TCollectibleLong(0));
          collection->Add(new TCollectibleLong(1));
          collection->Add(new TCollectibleLong(2));
          collection->Add(new TCollectibleLong(3));
      
          TCollectibleLong *p;
      
          p = collection->First();
          printf("Member First? =>%s\n", (collection->Member(*p) ? "yes" : "no"));
      
          p = new TCollectibleLong(4);
          printf("Member 4? =>%s\n", (collection->Member(*p) ? "yes" : "no"));
          delete p;
      
          p = new TCollectibleLong(3);
          printf("Member 3? =>%s\n", (collection->Member(*p) ? "yes" : "no"));
          delete p;
      
          collection->DeleteAll();
          delete collection;
          return 0;
      }
The output shows that Member now returns values that make sense according to the definition of equality selected for this particular job.

          Member First? =>yes
          Member 4? =>no
          Member 3? =>yes

[Contents] [Previous] [Next]
Click the icon to mail questions or corrections about this material to Taligent personnel.
Copyright©1995 Taligent,Inc. All rights reserved.

Generated with WebMaker