|
|||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionSTL provides a template based set of collection classes, and methods for working on those collections. The collection classes give the developer access to fast and efficient collections. While the methods, which are known as the algorithms, provide template based collection manipulations functions. The benefits of STL include
TemplatesIf you are already familiar with templates then skip to the next section. Otherwise read this section for a brief tutorial on templates. A template can be thought of as a macro with type checking. For example to declare a template we would do the following: template < class T > class Value { T _value; public: Value ( T value ) { _value = value; } T getValue (); void setValue ( T value ); }; template < class T > T Value<T>::getValue () { return _value; } template < class T > void Value<T>::setValue ( T value ) { _value = value; } This example declares a class Value, which stores a parameterized value, _value, of type T. After the keyword template, in the angled brackets, is a list of parameters. The list tells the template what types will be used in the template. A good analogy for the template parameter list is the parameter list for a class constructor. Like a constructor, the number of arguments for the template can be from one to many. Methods for a template that are declared outside the class definition require the template keyword, as shown above. To use the Value class to declare an array of floats we would do: Value<float> values[10]; // array of values of type float This declares an array of values, the angled brackets tells us that Value will store its value as a float. If we wanted to declare a list to work with our template based Value class we could do the following: Template < class T > class ValueList { Value<T> * _nodes. public: ValueList ( int noElements ) { _nodes = new Node<T>[noElements]; } virtual ~ValueList () { delete [] _nodes; } }; Here we have declared a template-based class that stores a variable sized list of values. STL Collection TypesEach STL collection type has its own template parameters, which will be discussed later. What type of collection you use is up to your needs and tastes. From past experience, the vector and map classes are the most useful. The vector class is ideal for simple and complex collection types, while the map class is used when an associative type of collection is needed. The deque collection is excellent for use in systems that have queued based processing, such as a message based system.
STL StringsSTL strings support both ascii and unicode character strings.
STL StreamsStreams provide the developer with classes that can output to a container variable types of stream elements.
To include the string class definitions use: #include <strstring> STL Collections General Class Methods
Standard Out and InputSTL also includes classes for printing to the standard output streams. Like standard C++ the classes are cout and wcout. To use them in a console application include the file iostream. As an example: #include <iostream> void main () { char ch; cin >> ch; cout << “This is the output terminal for STL” << endl; } Vector and Deque add and remove methodsWe want to look briefly at adding/removing elements from the vector and deque collections. These collections are represented as an array, and to add an element we use the push methods with back or front depending on if we are adding at the front (start) or back (end) of an array. The general methods are:
As an example, suppose we want to build a message processing system based on a message class: Class Msg
{
int _type;
int _priority;
string _message;
public:
Msg ( int type, int priority, string & msg )
{ _type = type; priority = priority; _msg = msg; }
Msg ( int type, int priority, char * msg )
{ _type = type; priority = priority; _msg = msg; }
int getType () { return _type; }
int getPriority () { reutrn _priority; }
string & getMsg () {return _msg; }
};
To store the messages we would need a first in first out based collection, such as deque: typedef deque<Msg> MsgList; To send a message we might do the following: Msg message( 0, 0, "My Message" ); msgList.push_back(msg); And to process message we could would do the following: void process_msgs () { bool done = false; while ( !done ) { // if no messages stop if ( msgList.size() == 0 ) { done == true; continue;} // get msg and process Msg & msg = msgList.front(); switch ( msg.getType() ) { // process messages } // remove msg from que msgList.pop_front(); } } With just a few lines of code we have created a general messaging system, if we wanted an entire system we could create a simple COM server that exposed a mail interface, and that stored the messages using a message list. Operator []For vector, map, deque, string and wstring collections, elements are normally added using: operator []
Access an element at a position, and for map, string and wstring supports insert of element. A simple example of using this operator would be to decalre a list using map: typedef map<int, string> StringList StringList strings; stringstream strStr for ( long i=0; i<10; i++ ) { stringstream strStr; strStr << "String " << i; strings[i] = strStr.str(); } for ( long i=0; i<10; i++ ) { string str = strings[5]; cout << str.c_str() << endl; } We have created a map, whose key is an integer, and that stores strings. IteratorsIterators support the access of elements in a collection. They are used throughout the STL to access and list elements in a container. The iterators for a particular collection are defined in the collection class definition. Below we list three types of iterators, iterator, reverse_iterator, and random access. Random access iterators are simply iterators that can go both forward and backward through a collection with any step value. For example using vector we could do the following: vector<int> myVec; vector<int>::iterator first, last; for ( long i=0; i<10; i++ ) myVec.push_back(i); first = myVec.begin(); last = myVec.begin() + 5; if ( last >= myVec.end() ) return; myVec.erase( first, last ); This code will erase the first five elements of the vector. Note, we are setting the last iterator to one past the last element we of interest, and we test this element against the return value of end (which give an iterator one past the last valid item in a collection). Always remember when using STL, to mark the end of an operation use an iterator that points to the next element after the last valid element in the operation. The three types of iterators are:
It is important to remember, when you get an iterator to a collection do not modify the collection and then expect to use the iterator. Once a collection has been modified an iterator in most cases will become invalid. Declaring collectionsEach collection uses it’s template paramters to determine what elements the collection will store. Shown below is a list of the collections we are discussing and beside each is the template pamaters. In the parameters T denotes the element type to store in the collection, A denotes the allocator (which allocates elements), Key denotes the key for the element, and Pred denotes how the collection will be sorted. template < class T, class A = allocator<T> > class vector template < class T, class A = allocator<T> > class list template < class T, class A = allocator<T> > class deque template <class Key, class T, class Pred = less<Key>, class A = allocator<T> > class map template <class Key, class Pred = less<Key>, class A = allocator<Key> > class set template <class Key, class T, class Pred = less<Key>, class A = allocator<T> > class multimap template <class Key, class Pred = less<Key>, class A = allocator<Key> > class multiset This list looks somewhat daunting but it provides a quick reference for the collections. In most cases you will use the default arguments and your only concern will be what you are storing and how it is stored. T refers to what you will store, and for collections that support a key; Key shows how the elements will be associated. From previous experience the vector, map and deque classes are the most often used so we can use them as an example for declaring a collection: Using typedef to declare the collection: typedef vector<int> myVector typedef map< string, int > myMap typedef deque< string > myQue The first declaration declares a vector of integers, the second declares a collection of integers, which have a key of type string, and the last declares a queue (or stack) of strings. Another way to declare a collection is to derive a collection from an STL collection as in the following: class myVector : public vector<int> {}; Either method is useful, it a matter of preference. Another important consideration is declaring the iterators supported by the collection as separate types. If we use the above example we would declare: typedef myVector::iterator vectorIterator typedef myVector::reverse_iterator revVectorIterator This gives the user of the collection direct access to the iterator without being forced to use the following syntax: myVector coll; for ( myVector::iterator element = coll.begin(); element < coll.end(); element++ ) The resolution operator can be cumbersome. AlgorithmsUp to this point we have discussed how to use STL at a bare minimum, now we need to delve into the most important part of the collections. How do we manipulate a collection? For example, if we had list of strings, what would we need to sort the list in alphabetical order, or if we wanted to search a collection for a set of elements that matched a given criterion. This is where STL algorithms are used. In your visual studio installation, under include directory, you will find an include file, algorithm. In algorithm a set of template based functions are declared. These functions can be used to manipulate STL collections. The functions can be categorized in the following: sequence, sorting and numeric. Using these categories, we can list all of the methods of algorithms:
Since this is an extensive list, we will only examine a few of the methods in the algorithms. It is very important to note that the methods here are templated so we are not required to use the STL containers to use the methods. For example, we could have a list of ints and to sort this list then we could do: #include <vector> #include <algorithm> #include <iostream> vector<int> myVec; vector<int>::iterator item; ostream_iterator<int> out(cout," "); // generate array for ( long i=0; i<10; i++ ) myVec.push_back(i); // shuffle the array random_shuffle( myVec.begin(), myVec.end() ); copy( myVec.begin(), myVec.end(), out ); // sort the array in ascending order sort( myVec.begin(), myVec.end() ); copy( myVec.begin(), myVec.end(), out ); This example shows how declare the vector and then sort it, using STL containers. We could do the same without using containers: ostream_iterator<int> out(cout," "); // generate array (note: one extra element, end in STL is one element past last valid) int myVec[11]; for ( long i=0; i<10; i++ ) myVec[i] = i; int * begin = &myVec[0]; int * end = &myVec[10]; // shuffle the array random_shuffle( begin, end ); copy( begin, end, out ); // sort the array in ascending order sort( begin, end ); copy( begin, end, out ); How you use the algorithms is largely up to you, but they provide a rich set of methods for manipulating containers. Multithreading IssuesSTL is not thread protected, so you must provide locks on your collections if they will be used in multithreaded environment. The standard locking mechanisms of Mutexes, Semaphores and Critical Sections can be used. One simple mechanism for providing locking is to declare a lock class. In this class the constructor creates the lock, and the destructor destroys the lock. Then provide lock() and unlock() methods. For example: class Lock { public: HANDLE _hMutex; // used to lock/unlock object Lock() : _hMutex(NULL) { _hMutex = ::CreateMutex( NULL, false,NULL) ); } virtual ~Lock() { ::CloseHandle( _hMutex ); } bool lock () { if ( _hMutex == NULL ) return false; WaitForSingleObject( _hMutex, INFINITE ); return true; } void unlock () { ReleaseMutex(_hMutex); } }; Then declare a class that is derived from one of the STL collections, and in the class override the access methods to the collection that might cause an insertion or deletion of an element. For example a general vector class would be: template <class T> class LockVector : vector<T>, Lock { public: LockVector () : vector<T>(), Lock() {} virtual LockVector () {} void insert ( T & obj ) { if ( !lock()) return; vector<T>::push_back (obj); unlock(); } }; ConclusionHopefully I’ve given you a good tutorial on how to use STL. If not then please try some of the web sites listed below or drop by your local bookstore or amazon.com and purchase one of the many books on the subject. I believe STL can provide many benefits and I hope you will to. Other STL Web sites
Direct Links:
Books about the STL and generic programmngSTL for C++ Programmers, by Leen Ammeraal. Designing Components with the C++ STL, by Ulrich Breymann. STL Tutorial and Reference Guide, by David Musser and Atul Saini. C++ Programmer’s Guide to the Standard Template Library, by Mark Nelson. | ||||||||||||||||||||||||||||||||