The object allocates and frees storage for the sequence it controls as individual nodes in a bidirectional linked list. To speed access, the object also maintains a varying-length array of pointers into the list (the hash table), effectively managing the whole list as a sequence of sublists, or buckets. It inserts elements into a bucket that it keeps ordered by altering the links between nodes, never by copying the contents of one node to another. That means you can insert and remove elements freely without disturbing remaining elements.
The object orders each bucket it controls by calling a stored delegate object of type hash_set::key_compare (STL/CLR). You can specify the stored delegate object when you construct the hash_set; if you specify no delegate object, the default is the comparison operator<=(key_type, key_type).
You access the stored delegate object by calling the member function hash_set::key_comp (STL/CLR)(). Such a delegate object must define equivalent ordering between keys of type hash_set::key_type (STL/CLR). That means, for any two keys X and Y:
key_comp()(X, Y) returns the same Boolean result on every call.
If key_comp()(X, Y) && key_comp()(Y, X) is true, then X and Y are said to have equivalent ordering.
Any ordering rule that behaves like operator<=(key_type, key_type), operator>=(key_type, key_type) or operator==(key_type, key_type) defines eqivalent ordering.
Note that the container ensures only that elements whose keys have equivalent ordering (and which hash to the same integer value) are adjacent within a bucket. Unlike template class hash_multiset (STL/CLR), an object of template class hash_set ensures that keys for all elements are unique. (No two keys have equivalent ordering.)
The object determines which bucket should contain a given ordering key by calling a stored delegate object of type hash_set::hasher (STL/CLR). You access this stored object by calling the member function hash_set::hash_delegate (STL/CLR)() to obtain an integer value that depends on the key value. You can specify the stored delegate object when you construct the hash_set; if you specify no delegate object, the default is the function System::Object::hash_value(key_type). That means, for any keys X and Y:
hash_delegate()(X) returns the same integer result on every call.
If X and Y have equivalent ordering, then hash_delegate()(X) should return the same integer result as hash_delegate()(Y).
Each element serves as both a key and a value. The sequence is represented in a way that permits lookup, insertion, and removal of an arbitrary element with a number of operations that is independent of the number of elements in the sequence (constant time) -- at least in the best of cases. Moreover, inserting an element invalidates no iterators, and removing an element invalidates only those iterators which point at the removed element.
If hashed values are not uniformly distributed, however, a hash table can degenerate. In the extreme -- for a hash function that always returns the same value -- lookup, insertion, and removal are proportional to the number of elements in the sequence (linear time). The container endeavors to choose a reasonable hash function, mean bucket size, and hash-table size (total number of buckets), but you can override any or all of these choices. See, for example, the functions hash_set::max_load_factor (STL/CLR) and hash_set::rehash (STL/CLR).
A hash_set supports bidirectional iterators, which means you can step to adjacent elements given an iterator that designates an element in the controlled sequence. A special head node corresponds to the iterator returned by hash_set::end (STL/CLR)(). You can decrement this iterator to reach the last element in the controlled sequence, if present. You can increment a hash_set iterator to reach the head node, and it will then compare equal to end(). But you cannot dereference the iterator returned by end().
Note that you cannot refer to a hash_set element directly given its numerical position -- that requires a random-access iterator.
A hash_set iterator stores a handle to its associated hash_set node, which in turn stores a handle to its associated container. You can use iterators only with their associated container objects. A hash_set iterator remains valid so long as its associated hash_set node is associated with some hash_set. Moreover, a valid iterator is dereferencable -- you can use it to access or alter the element value it designates -- so long as it is not equal to end().
Erasing or removing an element calls the destructor for its stored value. Destroying the container erases all elements. Thus, a container whose element type is a ref class ensures that no elements outlive the container. Note, however, that a container of handles does not destroy its elements.