As always, I welcome thoughtful comments, suggestions, corrections, ale, and offers of help via email at khan@xraylith.wisc.edu.
Copyright(c) 1995 Mumit Khan
I personally use GCC 2.7.0 (combined with Cygnus template repository patch) and ObjectSpace STL<ToolKit>, so please bear this in mind when you see something that doesn't make sense to your configuration. If you are using GCC 2.7.0 with ObjectSpace STL<ToolKit>, then you should get in touch with ObjectSpace for bug fixes (See here for more info).
Here I describe some "general guidelines" (read: this is what I do, your mileage may vary widely) on what constructors and operators one should define when working with STL.
If you don't have this, STL will use the compiler generated one (ie., member-wise copy), and embedded pointers will result in weird bugs.
See here for an in-depth look at why and when these are needed.
Better have this -- same effect as copy constructor.
if your class X is essentially un-ordered, simply return true or false. For example, if you have container full of pointers to some other object, ordering may not make sense either.
if your class X is essentially un-ordered, simply return true or false. For example, if you have container full of pointers to some other object, ordering may not make sense either.
The operators == and < are very important for sorted collections like set, map etc, and are the reason why storing pointers in STL sorted collections may not work as you would expect. See here for some caveats in storing pointers in STL containers. Even though I've shown the 2 operators as member functions here, they don't need be (which is great, since you can use somebody else's class without having to modify it). eg., if there is a library class X that you cannot modify, you can define the operators == and < externally as following:
Note that you may not need the < and == operators for some compilers if your code doesn't use the algorithms that need these operators (eg., sorting and such things). The compilers I deal with like to instantiate ALL the members when I manually instantiate templates, and so I'm out of luck. GCC 2.7.0 seems to do a much better job of instantiating only the needed members.
See here for an in-depth look at why and when these are needed.
What members do I have to define for an object that will live in an STL container?To explain this without having to go through the STL source code, let's examine how list<T> works. Here's a typical list usage scenario:
void f() { // <------- (1) class X { }; list<X> xlist; // <------- (2) // // now populate the list with lots of X's. // X x(); xlist.push_back(x); // <-------- (3) // // do what-not with xlist // // // now replace the 1st element with something else. // X x2; xlist.front() = x2; // <-------- (4) // // remove the element with value x3 // X x3; xlist.remove(x3); // <-------- (5) // // now sort the list // xlist.sort(); // <-------- (6) // // do stuff with xlist and then return from f() // return; } // <-------- (7)Now let's see what happens at steps (1) through (7).
template <class T> struct list_node { void_pointer next; void_pointer prev; T data; };To create list_node, the data member, T, must have a default constructor (or equivalent, such as a constructor with all default arguments) defined.
Step (2) requires a default constructor.
insert(begin(), x);And here's the code for a typical insert method:
iterator insert(iterator position, const T& x) { link_type tmp = get_node(); < ---- (3a) construct(value_allocator.address((*tmp).data), x); < ---- (3b) (*tmp).next = position.node; (*tmp).prev = (*position.node).prev; (*(link_type((*position.node).prev))).next = tmp; (*position.node).prev = tmp; ++length; return tmp; }
Step 3a requires the construction of a data member of type X as in the Step 1, and hence requires a default constructor. Step 2b uses the STL allocator interface to construct the object from argument x using placement new and hence requires a copy constructor. The typical construct looks like the following:
template <class T1, class T2> inline void construct(T1* p, const T2& value) { new (p) T1(value); }
Step (3) requires a copy constructor (3b) in addition to a default constructor (3a).
Step (4) requires an assignment operator, ie., operator=(const X& x).
template <class T> void list<T>::remove(const T& value) { iterator first = begin(); iterator last = end(); while (first != last) { iterator next = first; ++next; if (*first == value) // <-------- (5a) erase(first); first = next; } }The remove member starts in the beginning of the list and searches till either the end is reached or a value equalling value argument is found; if found, remove removes the element from the list and calls the destructor. Note in Step (6a), we need the equality operator (operator==) defined for this to work.
Step (5) requires an equality operator, ie., bool operator==(const X& x).
template <class T> void list<T>::merge(list<T>& x) { iterator first1 = begin(); iterator last1 = end(); iterator first2 = x.begin(); iterator last2 = x.end(); while (first1 != last1 && first2 != last2) if (*first2 < *first1) { // <-------- (6a) iterator next = first2; transfer(first1, first2, ++next); first2 = next; } else first1++; if (first2 != last2) transfer(last1, first2, last2); length += x.length; x.length= 0; } template <class T> void list<T>::sort() { if (size() < 2) return; list<T> carry; list<T> counter[64]; int fill = 0; while (!empty()) { carry.splice(carry.begin(), *this, begin()); int i = 0; while(i < fill && !counter[i].empty()) { counter[i].merge(carry); carry.swap(counter[i++]); } carry.swap(counter[i]); if (i == fill) ++fill; } while(fill--) merge(counter[fill]); }
Step (6) requires a less-than operator, ie., bool operator<(const X& x).
Step (7) requires a destructor
The following are automatically supplied by the compiler if not defined by the user:
If X in this example contains pointers, please be sure to define all of these instead of leaving it to the compiler.
The following must be defined explicitly by the user:
A better solution is to wrap the pointers in a simple class that you store in STL containers. For a trivial example, see here.
#include <stl.h> // or individual includes if you like // need list.h, set.h and algo.h #include <iostream.h> // // THIS IS VERY IMPORTANT (see note above). You have to tell the set // or multiset or map to compare the objects pointed to rather than // the pointers these containers are storing. // struct compare { bool operator() (const int* i1, const int* i2) const { return *i1 < *i2; } }; void print(int* i) { cout << " " << *i; } int main(int, char*[]) { list<int*> list1; // // create a list of new'd integers. // for(int i = 0; i < 5; ++i) { list1.push_back(new int(i * i)); } cout << "List of int*: ("; for_each(list1.begin(), list1.end(), print); cout << ")" << endl; // // now put these integers into a set. Note that I'm using a // custom comparator to compare the integers, not the pointers. // set<int*, compare> set1; copy(list1.begin(), list1.end(), insert_iterator<set<int*, compare> > (set1, set1.begin()) ); cout << "Set of int* : ["; for_each(set1.begin(), set1.end(), print); cout << "]" << endl; return 0; }
When you run the program, it should produce the following output. % ./testc++ List of int*: ( 0 1 4 9 16) Set of int* : [ 0 1 4 9 16]
Special Note for Borland C++ users: However, Ben Scherrey (scherrey@proteus-tech.com) points out that the OS/2 Borland C++ compiler cannot handle this, and I believe this is due the compiler's lack of support for explicitly calling template destructor. Ben says that the if you overload destroy and construct yourself, it works out. Thanks Ben.
void destroy(X** pointer ) { (*pointer)->~X(); } inline void construct(X** p, const X* value ) { new (p) (const X*)(value); }
template <class ForwardIterator, class ForwardIterator> void sequence_delete(ForwardIterator first, ForwardIterator last) { while (first != last) delete *first++; } template <class ForwardIterator, class ForwardIterator> void map_delete(ForwardIterator first, ForwardIterator last) { while (first != last) delete (*first++).second; } Map<int, SomeType*, less<int> > mymap_; // // populate my map with new'd SomeType's. // map_delete(mymap_.begin(), mymap_.end());ObjectSpace uses a non-standard release() member to achieve the above.
list<char*> means a list character pointers, NOT strings they point to. less<char*> will compare the pointers, NOT the strings (ie., NOT use strcmp and friends).
char buf[1024]; strcpy(buf, "THIS_WOULD_CHANGE_MAGICALLY"); list<char*> list1; list1.push_back(buf); ostream_iterator<char*> citer(cout, " "); copy(list1.begin(), list1.end(), citer); // you should see one string, the one in buf above. strcpy(buf, "YIKES!"); copy(list1.begin(), list1.end(), citer); // SURPRISE. your list is changed now.
In general, do not use char* as container objects, rather write a simple string class (ask me if you need one) and use that instead.
#include <stl.h> #include <iostream.h> int main(int, char*[]) { static char* names[] = {"one", "two", "three"}; set<char*, less<char*> > name_set; name_set.insert(names[0]); name_set.insert(names[1]); name_set.insert(names[2]); char buf[256]; strcpy(buf, "one"); const char* one = buf; set<char*, less<char*> >::const_iterator it = name_set.find(one); if (it == name_set.end()) { cerr << "No such name `" << one << "' in set!" << endl; } else { cerr << "Found name `" << one << "' in set." << endl; } return 0; }
#include <stl.h> #include <iostream.h> // // Let's say you want to put pointers to X into multiple STL containers. // class X { public: X(int i) : i_(i) { ; } X(const X& x) : i_(x.i_) { } ~X() { } X& operator= (const X& x) { i_ = x.i_; } int operator()() const { return i_; } private: int i_; }; bool operator== (const X& x1, const X& x2) { return x1() == x2(); } bool operator< (const X& x1, const X& x2) { return x1() < x2(); } // // Define a simple wrapper class to put into STL containers. This one // simple wraps X. // class XPtrWrapper { public: XPtrWrapper(X* x = 0) : x_(x) { } XPtrWrapper(const XPtrWrapper& xw) : x_(xw.x_) { } ~XPtrWrapper() { } XPtrWrapper& operator= (const XPtrWrapper& xw) { x_ = xw.x_; } const X* operator() () const { return x_; } X* operator() () { return x_; } private: X* x_; }; bool operator== (const XPtrWrapper& xw1, const XPtrWrapper& xw2) { return (xw1.operator()() && xw2.operator()()) ? *xw1() == *xw2() : false; } bool operator< (const XPtrWrapper& xw1, const XPtrWrapper& xw2) { return (xw1() && xw2()) ? *xw1() < *xw2() : false; } void print(const XPtrWrapper& xw) { cout << " " << (*xw())(); } int main(int, char*[]) { XPtrWrapper bucket[5]; for(int i = 0; i < 5; ++i) { bucket[i] = XPtrWrapper(new X(i * i)); } random_shuffle(bucket, bucket + 5); list<XPtrWrapper> list1; copy(bucket, bucket + 5, back_insert_iterator<list<XPtrWrapper> > (list1) ); cout << "List of XPtrWrapper: ("; for_each(list1.begin(), list1.end(), print); cout << ")" << endl; // // now put these XPtrWrappers into a set. Note that I can use // greatersince I've defined operator> for it. // set<XPtrWrapper, greater<XPtrWrapper> > set1; copy(list1.begin(), list1.end(), insert_iterator<set<XPtrWrapper, greater<XPtrWrapper> > > (set1, set1.begin()) ); cout << "Set of XPtrWrapper : ["; for_each(set1.begin(), set1.end(), print); cout << "]" << endl; // // now put these integers into a deque. // deque<XPtrWrapper> deque1; copy(list1.begin(), list1.end(), back_insert_iterator<deque<XPtrWrapper> > (deque1) ); cout << "Deque of XPtrWrapper : ("; for_each(deque1.begin(), deque1.end(), print); cout << ")" << endl; return 0; }
And the output is:
List of XPtrWrapper: ( 4 0 16 1 9) Set of XPtrWrapper : [ 16 9 4 1 0] Deque of XPtrWrapper : ( 4 0 16 1 9)
Note: After the new'd Base derivative is passed to the wrapper, it owns it and deletes it in the destructor.
#include <stl.h> #include <string.h> #include <iostream.h> // // abstract base class // class Base { public: const char* typename() const { return typename_; } virtual Base* clone() const = 0; virtual void identify(ostream& os) const = 0; virtual ~Base(); public: static int count; protected: Base(const char* typename); Base(const Base& base); private: char* typename_; }; Base::Base(const char* typename) { const char* tname = (typename) ? typename : "unknown"; strcpy(typename_ = new char[strlen(tname) + 1], tname); ++count; } Base::Base(const Base& base) { strcpy( typename_ = new char[strlen(base.typename_) + 1], base.typename_ ); ++count; } Base::~Base() { delete[] typename_; --count; } // // First derived class. // class Derived1 : public Base { public: Derived1(int data) : Base("derived1"), data_(data) { } Derived1(const Derived1& d) : Base("derived1"), data_(d.data()) { } virtual ~Derived1() { } virtual Base* clone() const { return new Derived1(*this); } virtual void identify(ostream& os) const; int data() const { return data_; } private: int data_; }; virtual void Derived1::identify(ostream& os) const { os << "(" << typename() << " " << data() << ")"; } // // Second derived class. // class Derived2 : public Base { public: Derived2(int data) : Base("derived2"), data_(data) { } Derived2(const Derived2& d) : Base("derived2"), data_(d.data()) { } virtual ~Derived2() { } virtual Base* clone() const { return new Derived2(*this); } virtual void identify(ostream& os) const; int data() const { return data_; } private: int data_; }; virtual void Derived2::identify(ostream& os) const { os << "(" << typename() << " " << data() << ")"; } // // now define the pointer wrapper. // class BaseWrapper { public: BaseWrapper(Base* base_ptr = 0) : base_ptr_(base_ptr) { } BaseWrapper(const BaseWrapper& bw) { base_ptr_ = bw() ? bw()->clone() : 0; } ~BaseWrapper() { delete base_ptr_; } const Base* operator()() const { return base_ptr_; } Base* operator()() { return base_ptr_; } BaseWrapper& operator= (const BaseWrapper& bw) { delete base_ptr_; base_ptr_ = bw()->clone(); } private: Base* base_ptr_; }; bool operator== (const BaseWrapper& bw1, const BaseWrapper& w2) { return false; } bool operator< (const BaseWrapper& bw1, const BaseWrapper& w2) { return false; } // // end of class defs. // // define static members. int Base::count = 0; int main(int, char*[]) { list<BaseWrapper> list1; list1.push_back(BaseWrapper(new Derived1(101))); list1.push_back(BaseWrapper(new Derived2(201))); list1.push_back(BaseWrapper(new Derived2(202))); list1.push_back(BaseWrapper(new Derived1(102))); list1.push_back(BaseWrapper(new Derived2(203))); list<BaseWrapper>::const_iterator it = list1.begin(); for(; it != list1.end(); ++it) { const BaseWrapper& bw = *it; bw()->identify(cerr); cerr << " "; } cerr << endl << endl; return 0; }
(derived1 101) (derived2 201) (derived2 202) (derived1 102) (derived2 203)
Note: After the new'd Base derivative is passed to the wrapper, it owns it and deletes it in the destructor.
#include <stl.h> #include <string.h> #include <iostream.h> // // abstract base class // class Base { public: const char* typename() const { return typename_; } virtual Base* clone() const = 0; virtual void identify(ostream& os) const = 0; virtual ~Base(); public: static int count; protected: Base(const char* typename); Base(const Base& base); private: char* typename_; }; Base::Base(const char* typename) { const char* tname = (typename) ? typename : "unknown"; strcpy(typename_ = new char[strlen(tname) + 1], tname); ++count; } Base::Base(const Base& base) { strcpy( typename_ = new char[strlen(base.typename_) + 1], base.typename_ ); ++count; } Base::~Base() { delete[] typename_; --count; } // // First derived class. // class Derived1 : public Base { public: Derived1(int data) : Base("derived1"), data_(data) { } Derived1(const Derived1& d) : Base("derived1"), data_(d.data()) { } virtual ~Derived1() { } virtual Base* clone() const { return new Derived1(*this); } virtual void identify(ostream& os) const; int data() const { return data_; } private: int data_; }; virtual void Derived1::identify(ostream& os) const { os << "(" << typename() << " " << data() << ")"; } // // Second derived class. // class Derived2 : public Base { public: Derived2(int data) : Base("derived2"), data_(data) { } Derived2(const Derived2& d) : Base("derived2"), data_(d.data()) { } virtual ~Derived2() { } virtual Base* clone() const { return new Derived2(*this); } virtual void identify(ostream& os) const; int data() const { return data_; } private: int data_; }; virtual void Derived2::identify(ostream& os) const { os << "(" << typename() << " " << data() << ")"; } // // now define a templated pointer wrapper. The class must support the // clone() method. // template <class T> class PtrWrapper { public: PtrWrapper(T* t_ptr = 0) : t_ptr_(t_ptr) { } PtrWrapper(const PtrWrapper<T>& w) { t_ptr_ = w() ? w()->clone() : 0; } ~PtrWrapper() { delete t_ptr_; } const T* operator()() const { return t_ptr_; } T* operator()() { return t_ptr_; } PtrWrapper<T>& operator= (const PtrWrapper<T>& w) { delete t_ptr_; t_ptr_ = w()->clone(); return *this; } private: T* t_ptr_; }; template <class T> bool operator== (const PtrWrapper<T>& w1, const PtrWrapper<T>& w2) { return false; } template <class T> bool operator< (const PtrWrapper<T>& w1, const PtrWrapper<T>& w2) { return false; } // // end of class defs. // // define static members. int Base::count = 0; int main(int, char*[]) { list<PtrWrapper<Base> > list1; list1.push_back(PtrWrapper<Base> (new Derived1(101))); list1.push_back(PtrWrapper<Base> (new Derived2(201))); list1.push_back(PtrWrapper<Base> (new Derived2(202))); list1.push_back(PtrWrapper<Base> (new Derived1(102))); list1.push_back(PtrWrapper<Base> (new Derived2(203))); list<PtrWrapper<Base> >::const_iterator it = list1.begin(); for(; it != list1.end(); ++it) { const PtrWrapper<Base>& w = *it; w()->identify(cerr); cerr << " "; } cerr << endl << endl; return 0; }
(derived1 101) (derived2 201) (derived2 202) (derived1 102) (derived2 203)
typedef Map<int, X*, less<int> > XMap; XMap xmap; // // populate xmap will what-not. // const X* xx = xmap[5]; if (xx == 0) { // not in map. do_something(); } else { do_something_else(); }
looks pretty innocuous, but what really happens is that a new entry for xmap[5] is created and gets stuffed with a NULL pointer which causes amazing amount of headache 10,000 lines of code later. The right way of course (and documented in the fine manual) is the following:
typedef Map<int, X*, less<int> > XMap; XMap xmap; // // populate xmap will what-not. // XMap::const_iterator it = xmap.find(5); if (it == xmap.end()) { // not in map. do_something(); } else { do_something_else(); }
#include <stl.h> #include <iostream.h> typedef map<char*, unsigned long, less<char*> > Phonebook; static void print_phbook(ostream& os, const Phonebook& map_) { for(Phonebook::const_iterator i = map_.begin(); i != map_.end(); ++i) { os << "\t(" << (*i).first << " " << (*i).second << ") " << endl; } } static void change_phno( Phonebook& phbook, const char* name, unsigned long phno ) { char buf[1024]; strcpy(buf, name); phbook[(char*)buf] = phno; }; int main(int, char*[]) { Phonebook phbook; phbook[(char*)"grumpy"] = 5551212; phbook[(char*)"sleepy"] = 5552121; phbook[(char*)"gandhi"] = 5554242; cerr << "==> Initial phone book" << endl; print_phbook(cerr, phbook); cerr << endl; change_phno(phbook, "grumpy", 9995152); cerr << "==> Grumpy moved. The new number is 9995152" << endl; print_phbook(cerr, phbook); cerr << endl; char buf[1024]; strcpy(buf, "grumpy"); phbook[(char*)buf] = 7621212; cerr << "==> Grumpy moved again! latest number 7621212" << endl; print_phbook(cerr, phbook); cerr << endl; return 0; }
And here's the output (might even dump core with some STL implementations):
==> Initial phone book (grumpy 5551212) (sleepy 5552121) (gandhi 5554242) ==> Grumpy moved. The new number is 9995152 (grumpy 5551212) (sleepy 5552121) (gandhi 5554242) ( 9995152) ==> Grumpy moved again! latest number 7621212 (grumpy 5551212) (sleepy 5552121) (gandhi 5554242) ( 9995152) (grumpy 7621212)
First off. thank you for maintaining such a useful page on STL. It has filled the documentation gap for me twice when I could'nt figure out the Modena manual ! A question that I cannot get a handle on yet is how to search for element in a list without passing the full element in.. Example: If I have a list or vector of regions which contain a pointer to a buffer. vector<Region> and Region has a method for determining whether or not it contains a specific pointer in memory. bool Region::Contains(Byte* Bfr) What I thought I should be able to do is use Region = find(List.begin(), List.end(), Byte* BfrToBeSearchedFor); or something like it. But STL (in my probably limited understanding) seems to require that I use Region = find(List.begin(), List.end(), RegionToBeSearchedFor); or Region = find(List.begin(), List.end(), ComparisonFunction); Neither of which appears to answer my needs. If you have any suggestions on how this would be tackled please put them in your Newbie guide. If not I will try posting to comp.lang.c++. Thank you again. Jim Jackl-Mochel
The first thing to note is that the appropriate algorithm to use is find_if, not find; now, we also need to supply a predicate (has_buffer shown below) that will do the right thing. The following solution works in this case:
class Region { public: // ... bool Contains(Byte* Bfr) const; private: // ... }; list<Region> regions; // // populate regions with all the Region objects containing unique // Byte* elements, one of which may contain the Byte* you're looking // for. // Byte* BfrToBeSearchedFor = INITIALIZE_BUFFER_POINTER; // // find the region which contains the given buffer pointer bufferp; // // first, let's define the appropriate comparator fct. // struct has_buffer { Byte* buffer_; has_buffer(const Byte* buffer) : buffer_(buffer) { } bool operator()(const Region& region) const { return region.Contains(buffer_); } }; list<Region>::const_iterator it = find_if(regions.begin(), regions.end(), has_buffer(BfrToBeSearchedFor) ); if (it == regions.end()) { // not found } else { // found it const Byte* Bfr = *it; // ... }Notice the use of find_if, which takes a predicate that you can supply to find the element.
deque<int*> deque1; // // populate deque1 // // // now sort. (THIS IS INCORRECT) // sort(deque1.begin(), dequeu1.end());In the code sample above, the sort algorithm will use the default less<int*> function object to do the ordering and the result is obviously not the guranteed to be correct. There are two different approaches we can take -- define a set of comparator functions that work on pointers by dereferencing the arguments first (ala ObjectSpace STL<ToolKit>), define a "dereferencing" function object that works on unary and binary functions.
The following example shows how to do a custom pointer comparator.
bool intp_less(const int* i1, const int* i2) { return *i1 < *i2; } sort(deque1.begin(), dequeu1.end(), intp_less);Or, we can use a bit more structured method:
template <class BinaryFunction> class binary_dereference : binary_function< BinaryFunction::first_argument_type, BinaryFunction::second_argument_type, BinaryFunction::result_type> { public: binary_dereference( const BinaryFunction& func = BinaryFunction() ) : func_(func) { } BinaryFunction::result_type operator() ( BinaryFunction::first_argument_type* const& x, BinaryFunction::second_argument_type* const& y ) const { return func_(*x, *y); } protected: BinaryFunction func_; }; template <class BinaryFunction> inline binary_dereference<BinaryFunction> dereference2 (const BinaryFunction& func) { return binary_dereference<BinaryFunction>(func); } // // populate deque<int*> // deque<int*> deque1; // // now we sort ... // sort( deque1.begin(), deque1.end(), binary_dereference<less<int> > (less<int>()) ); // // or use the adapter // sort( deque1.begin(), deque1.end(), dereference2 (less<int> ()) );To use a set, you could always do the following:
typedef binary_dereference<less<int> > ip_compare; typedef set<int*, ip_compare> ip_set;Or, the following is even more structured:
template <class BinaryFunction, class Modifier> class binary_arg_modifier : binary_function< Modifier::argument_type, Modifier::argument_type, BinaryFunction::result_type> { public: binary_arg_modifier( const BinaryFunction& func = BinaryFunction(), const Modifier& modifier = Modifier() ) : func_(func), modifier_(modifier) { } BinaryFunction::result_type operator() ( const Modifier::argument_type& x, const Modifier::argument_type& y ) const { return func_(modifier_(x), modifier_(y)); } protected: BinaryFunction func_; Modifier modifier_; }; template <class BinaryFunction, class Modifier> inline binary_arg_modifier<BinaryFunction, Modifier> arg_modifier2 (const BinaryFunction& func, const Modifier& modifier) { return binary_arg_modifier<BinaryFunction, Modifier>(func, modifier); } template <class T> struct dereference : unary_function<T*, T> { T operator() (const T* x) const { return *x; } }; // // populate deque<int*> // deque<int*> deque1; // // now we sort ... // sort( deque1.begin(), deque1.end(), binary_arg_modifier< less<int>, dereference<int> > () ); // // or use the adapter // sort( deque1.begin(), deque1.end(), arg_modifier2 (less<int> (), dereference<int> ()) );You can of course use for set, map, etc as well.
typedef binary_arg_modifier< less<int>, dereference<int> > ip_compare; typedef set<int*, ip_compare> ip_set; ip_set set1;
class Appointment { public: // // define all the usual members ... // bool today(Date const& date) const; private: // // private stuff here. // }; typedef list<Appointment> Appointments; Appointments appt_book; // // define a general function // void print_todays_appt(Appointment& const appt) { if (appt.today(date)) print_appt(appt); } // // and here's how it's used. // for_each(appt_book.begin(), appt_book.end(), print_todays_appt);
Another common scenario is when you would like to modify each of the container elements within a given range; eg., let's say you would like to negate all the elements of the list. The following code shows how:
// // define an appropriate algorithm that will modify the element // in place calling the supplied function object. // template <class OutputIterator, class UnaryOperation> void modify_element(OutputIterator first, OutputIterator last, UnaryOperation op) { while (first != last) { *first = op(*first); ++first; } } list<int> list1; // // populate list1 with what-not. // modify_element(list1.begin(), list1.end(), negate<int>());
Predicates, Comparators and General Functions
Back to index
Why point beyond the end of the container? Why not point at the last item? The reason is quite simple: Remember that STL containers emulate (or try to as faithfully as possible) C pointer semantic and end() returns the equivalent of C 0 pointer.
Consider how else you would do the following if end() instead returned the last item in the container.
MyMap::const_iterator it = my_map.find(key); if (it == my_map.end()) no_such_key(); else found_key();
or,
bool empty(const STL_Container& container) { return container.begin() == container.end(); }
#include <stl.h> void foo(const list<int>& list1) { list<int>::iterator it = list1.begin(); for(; it != list1.end(); ++it) { ; } }
and here's the error message from GNU C++ 2.6.3:
g++ -g -I/usr/local/ospace -I/usr/local/ospace/include -c const_iterator.cc -o const_iterator.o const_iterator.cc: In function `void foo(const class list<int> &)': const_iterator.cc:5: no matching function for call to `list_iterator<int>::list_iterator<int> (list_const_iterator<int>)' /usr/local/ospace/ospace/stl/list.h:5: candidates are: list_iterator<int>::list_iterator(const list_iterator<int> &) /usr/local/ospace/ospace/stl/list.h:79: list_iterator<int>::list_iterator() /usr/local/ospace/ospace/stl/list.h:131: list_iterator<int>::list_iterator(os_list_node<int> *) const_iterator.cc:5: in base initialization for class `list_iterator<int>' const_iterator.cc:6: no conversion from `list_iterator<int>' and `list_const_iterator<int>' to types with default `operator !=' gmake: *** [const_iterator.o] Error 1
Of course the correct way is to use list<int>::const_iterator instead as shown here:
#include <stl.h> void foo(const list<int>& list1) { list<int>::const_iterator it = list1.begin(); for(; it != list1.end(); ++it) { ; } }
// // slightly modified STL standard sort() algorithm to not exclude // containers that do not support Random Access Iterators. Uses the // same interface as HP reference sort(first, last) interface. // // This code used with libg++ STL tweaks the infamous "template // unification failed" problem in all version of, so caveat emptore. // OS STL<ToolKit> works around this gcc problem. // // Mumit Khan <khan@xraylith.wisc.edu> // // #include <stl.h> template <class RandomAccessIterator, class T> inline void __generalized_sort ( RandomAccessIterator first, RandomAccessIterator last, random_access_iterator_tag, T* ) { sort(first, last); } // // highly inefficient, but proves the point. // template <class BidirectionalIterator, class T> inline void __generalized_sort ( BidirectionalIterator first, BidirectionalIterator last, bidirectional_iterator_tag, T* ) { deque<T> deque1; copy(first, last, back_inserter(deque1)); sort(deque1.begin(), deque1.end()); copy(deque1.begin(), deque1.end(), first); } template <class BidirectionalIterator> inline void generalized_sort ( BidirectionalIterator first, BidirectionalIterator last ) { __generalized_sort( first, last, iterator_category(first), value_type(first) ); }
Consider the following:
list<int> list1; // // populate list1 with what-not. // list<int> list2; // DOESN'T WORK. copy(list1.begin(), list1.end(), list2.begin()); // this works because back_list_iterator invokes push_back() which // dynamically resizes the list as appropriate. Note that if the // target were a set, inserter would've been // the appropriate one. copy(list1.begin(), list1.end(), back_list_iterator<int> (list2));
#include <stl.h> #include <math.h> #include <iostream.h> typedef map<int, float, less<int> > MyMap; void dump_map(ostream& os, const MyMap& map_) { for(MyMap::const_iterator i = map_.begin(); i != map_.end(); ++i) { os << "(" << (*i).first << " " << (*i).second << ") "; } } int main(int, char*[]) { MyMap map1; for(int i = 1; i < 5; ++i) map1[i] = sqrt(i); MyMap map2; for(i = 10; i < 15; ++i) map2[i] = sqrt(i); cerr << "==> Map1" << endl; dump_map(cerr, map1); cerr << endl << endl; cerr << "==> Map2" << endl; dump_map(cerr, map2); cerr << endl << endl; copy(map2.begin(), map2.end(), insert_iterator<MyMap> (map1, map1.begin()) ); cerr << "==> Map1 += Map2" << endl; dump_map(cerr, map1); cerr << endl << endl; return 0; }
For example, you can create copy_if from remove_copy_if by negating the predicate that you supply to remove_copy_if().
// // fill up a list with some numbers. // const int list1[] = {18, 3, 21, 3, 24, 3}; const int npoints = sizeof(list1)/sizeof(list1[0]); cout << endl << "===> Initial list" << endl; ostream_iterator<int> citer(cout, "\n"); copy(list1, list1 + npoints, citer); // // create a new sequence with all elements but 3's from list1. Note // that we initialize the result container to be as large as the // original one and use a simple output iterator. // cout << endl << "===> Removing all 3's" << endl; list<int> list2(npoints); list<int>::iterator iter = remove_copy_if( list1, list1 + npoints, list2.begin(), bind2nd(equal_to<int> (), 3) ); copy(list2.begin(), iter, citer); cout << "===" << endl; // // create a new sequence with all elements from list1, but 3's. Note // that we use a front_insert_iterator in this case. // cout << endl << "===> Removing all but 3's" << endl; list<int> list3; front_insert_iterator<list<int> > out_iter(list3); iter = remove_copy_if( list1, list1 + npoints, out_iter, not1 (bind2nd(equal_to<int> (), 3)) ); copy(list3.begin(), iter, citer); cout << "===" << endl;
Performance Note: When you have a choice between a list and a dequeu for some adaptor, I have found dequeu to give better, and in some cases, much better, performance.
#include <stl.h> #include <iostream.h> int main () { typedef stack<deque<int> > IntStack; IntStack istack; istack.push (31); istack.push (87); istack.push (13); istack.push (29); while (!istack.empty ()) { cout << istack.top () << endl; istack.pop (); } return 0; }
And the output:
29 13 87 31
#include <stl.h> #include <iostream.h> int main () { typedef queue<deque<int> > IntQueue; IntQueue iqueue; iqueue.push (31); iqueue.push (87); iqueue.push (13); iqueue.push (29); while (!iqueue.empty()) { cout << iqueue.front() << endl; iqueue.pop (); } return 0; }
And the output:
31 87 13 29
#include <stl.h> #include <iostream.h> int main () { typedef priority_queue<deque<int>, less<int> > IntPQueue; IntPQueue ipqueue; ipqueue.push (31); ipqueue.push (87); ipqueue.push (13); ipqueue.push (29); while (!ipqueue.empty()) { cout << ipqueue.top() << endl; ipqueue.pop(); } return 0; }
And the output:
87 31 29 13
#include <stl.h> #include <iostream.h> static bool even(int i) { return i % 2 == 0; } int main(int, char*[]) { list<int> list1; for(int i = 0; i < 10; ++i) { list1.push_back(i * i); } ostream_iterator<int> out(cout, " "); cout << "==> Initial list (x(i) = i**2): ["; copy(list1.begin(), list1.end(), out); cout << "]" << endl; list<int>::iterator end = remove_if(list1.begin(), list1.end(), even); cout << "==> After removing even numbers (Surprise!): ["; copy(list1.begin(), list1.end(), out); cout << "]" << endl; list1.erase(end, list1.end()); cout << "==> After erasing removed numbers: ["; copy(list1.begin(), list1.end(), out); cout << "]" << endl; return 0; }
And here's the output:
==> Initial list (x(i) = i**2): [0 1 4 9 16 25 36 49 64 81 ] ==> After removing even numbers (Surprise!): [1 9 25 49 81 25 36 49 64 81 ] ==> After erasing removed numbers: [1 9 25 49 81 ]
// // Sample STL program to show how to iterate thru list of list's. // #include <stl.h> #include <iostream.h> // // convenience typedefs to save typing. // typedef list<int> List; typedef list<List> ListOfList; void print_list(const List& list1, int id) { ostream_iterator<int> out(cout, " "); cout << "list " << id << ": "; copy(list1.begin(), list1.end(), out); cout << endl; } int main () { ListOfList list_of_list1; // // create the list of list: total of 3 lists, each with 4 members. // for(int i = 0; i < 3; ++i) { List list1; for(int j = 0; j < 4; ++j) { list1.push_back(i * 4 + j); } print_list(list1, i+1); list_of_list1.push_back(list1); } cout << endl; // // now iterator thru list of lists. // ListOfList::iterator it = list_of_list1.begin(); for(int j = 1; it != list_of_list1.end(); ++it, ++j) { const List& list1 = *it; print_list(list1, j); } return 0; }
And the output:
list 1: 0 1 2 3 list 2: 4 5 6 7 list 3: 8 9 10 11 list 1: 0 1 2 3 list 2: 4 5 6 7 list 3: 8 9 10 11
There are 2 things to look out for:
#include <iostream.h> #include <stdlib.h> #include <stl.h> #include <string.h> inline char* strnew(const char* str) { char* newstr = 0; if (str) { newstr = new char[strlen(str) + 1]; strcpy(newstr, str); } return newstr; } struct ListElem { int id_; char* name_; ListElem() : id_(0), name_(strnew("unknown")) { } ListElem(const ListElem& elem) : id_(elem.id_), name_(strnew(elem.name_)) { } ListElem(int id, const char* name) : id_(id), name_(strnew(name)) { } ~ListElem() { delete[] name_; } ListElem& operator= (const ListElem& elem) { id_ = elem.id_; delete[] name_; name_ = strnew(elem.name_); } bool operator< (const ListElem& elem) const { return id_ < elem.id_; } bool operator== (const ListElem& elem) const { return id_ == elem.id_; } void print(ostream& os) const { os << "(" << id_ << " " << name_ << ")"; } }; void print_list(ostream& os, const list<ListElem>& list1) { for( list<ListElem>::const_iterator it = list1.begin(); it != list1.end(); ++it ) { const ListElem& elem = *it; elem.print(os); } } int main(int, char*[]) { list<ListElem> list1; list1.push_back(ListElem(5, strnew("5"))); list1.push_back(ListElem(0, strnew("0"))); list1.push_back(ListElem(99, strnew("99"))); list1.push_back(ListElem(-1, strnew("-1"))); list1.push_back(ListElem(31, strnew("31"))); cerr << "Initial list: "; print_list(cerr, list1); cerr << endl; // // cannot use sort(list1.begin(), list1.end()) here; instead, must use // sort() member of list class. Hint: lists don't support random access // iterators. // list1.sort(); cerr << " Sorted list: "; print_list(cerr, list1); cerr << endl; return 0; }
% ./stl-list-sort Initial list: (5 "5")(0 "0")(99 "99")(-1 "-1")(31 "31") Sorted list: (-1 "-1")(0 "0")(5 "5")(31 "31")(99 "99")
#include <iostream.h> #include <stdlib.h> #include <stl.h> #include <string.h> inline char* strnew(const char* str) { char* newstr = 0; if (str) { newstr = new char[strlen(str) + 1]; strcpy(newstr, str); } return newstr; } struct ListElem { int id_; char* name_; ListElem() : id_(0), name_(strnew("unknown")) { } ListElem(const ListElem& elem) : id_(elem.id_), name_(strnew(elem.name_)) { } ListElem(int id, const char* name) : id_(id), name_(strnew(name)) { } ~ListElem() { delete[] name_; } ListElem& operator= (const ListElem& elem) { id_ = elem.id_; delete[] name_; name_ = strnew(elem.name_); } bool operator< (const ListElem& elem) const { return id_ < elem.id_; } bool operator== (const ListElem& elem) const { return id_ == elem.id_; } void print(ostream& os) const { os << "(" << id_ << " " << name_ << ")"; } }; void print_vector(ostream& os, const vector<ListElem>& vector1) { for( vector<ListElem>::const_iterator it = vector1.begin(); it != vector1.end(); ++it ) { const ListElem& elem = *it; elem.print(os); } } int main(int, char*[]) { vector<ListElem> vector1; vector1.push_back(ListElem(5, strnew("5"))); vector1.push_back(ListElem(0, strnew("0"))); vector1.push_back(ListElem(99, strnew("99"))); vector1.push_back(ListElem(-1, strnew("-1"))); vector1.push_back(ListElem(31, strnew("31"))); cerr << "Initial vector: "; print_vector(cerr, vector1); cerr << endl; sort(vector1.begin(), vector1.end()); cerr << " Sorted vector: "; print_vector(cerr, vector1); cerr << endl; return 0; }
% ./stl-list-sort Initial list: (5 "5")(0 "0")(99 "99")(-1 "-1")(31 "31") Sorted list: (-1 "-1")(0 "0")(5 "5")(31 "31")(99 "99")
#include <stl.h> // or individual includes if you like #include <iostream.h> int main(int, char*[]) { const unsigned ncards = 5; int cards[ncards]; for(int i = 0; i < ncards; ++i) { cards[i] = i + 1; } // print out the initial list (should be 1 ... ncards). ostream_iterator<int> out(cout, " "); cout << "initial: "; copy(cards, cards + ncards, out); cout << endl; // shuffle and print out the shuffled list random_shuffle(cards, cards + ncards); cout << "shuffled: "; copy(cards, cards + ncards, out); cout << endl; return 0; }
initial: 1 2 3 4 5 shuffled: 3 1 5 2 4
Miscellaneous examples/notes
Back to index
template <class InputIterator, class T> void __do_something(InputIterator first, InputIterator last, T*) { T tmp; // // now we can create variables of type T! // } template <class InputIterator> void do_something(InputIterator first, InputIterator last) { __do_something(first, last, value_type(first)); }
Miscellaneous examples/notes
Back to index
I'm making available an extremely simple and crude implementation of Persistent STL using libg++-2.6.2 hacked STL as the base implementation. Send me email if you'd like to know more about it. My current prototype is based on Texas Persistent Store v0.3 from UT-Austin OOPS research group. Click here for a GNU zip'd and tar'd copy of the current prototype (v0.1.1).
I've been using ObjectSpace STL<ToolKit> with gcc-2.6.3 quite heavily in the last few weeks and it turns out to be pretty solid. You obviously only get what GCC supports, but ObjectSpace did a pretty good job in squeezing out everything possible out of GCC in their implementation. I've run into a few problems (other than the well-known ones mentioned in the release notes), but there are easy work-arounds. Recently I have switched to ObjectSpace STL<ToolKit< with GCC 2.7.0.
GCC 2.7.0 users: ObjectSpace is providing bug fixes for STL <ToolKit> version 1.1 to make it work GCC 2.7.0. Please get in touch with ObjectSpace tehnical support for more information on how to acquire the fixes.
If you're using GCC 2.6.3, two things before you start:
If you're using GCC 2.7.0, two things before you start:
Things in favor of ObjectSpace implementation:
If you have templated code (eg., when using STL) and you're stuck with GCC, there are three ``somewhat portable'' ways to go (I don't like weird #pragma directives that are compiler specific):
One major complication with GCC is that all the template definitions must be visible at the point of instantiation (which may not be the case with Borland, but I wouldn't know about that), and if you have the template class members defined in a .cc file that is not included in the declaration file (.h), then you'll get undefined errors. The simple trick there is to conditionally include the .cc file in the template header .h file with some preprocessor directive that gets set for compilers like gcc. Take the following example:
=== template.h template <class T> class XX { public: XX(T t) : t_(t) { } T value() const; // declaration only, defn not inline. private: T t_; }; ==== template.cc #include "template.h" template <class T> T XX<T>::value() const { return t_; } === main.cc #include <iostream.h> #include "template.h" int main (int, char*[]) { XX<int> xx(5); cerr << "value = " << xx.value() << endl; return 0; } ===
This will *NOT* work for gcc unless you also include template.cc at the end of template.h. You'll get undefined error that looks something like:
ld: Undefined symbol XX<int>::value(void) const collect2: ld returned 2 exit statusTo get around this, you can always do the following:
=== template.h template <class T> class XX { public: XX(T t) : t_(t) { } T value() const; private: T t_; }; #if INCLUDE_TEMPLATE_DEFINITION # include "template.cc" #endif ===and somewhere else (config.h or via makefile), define the cpp symbol INCLUDE_TEMPLATE_DEFINITION when using GCC. Or something like the following should work as well:
#if __GNUC__ <= 2 && __GNUC_MINOR__ <= 7 # define INCLUDE_TEMPLATE_DEFINITION #endifNow gcc will be able to do what you need since it can see the definition when it tries to instantiate XX<int> in main.cc.
Let's start with a trivial program, where a list is the only STL data type used, specifically list<int>. For the examples here, I'm assuming ObjectSpace STL<ToolKit> with GCC 2.6.3 appropriately patched with Jason Merrill template fix (the same idea applies to FSF/GNU STL, but the particular set of templates that need to be instantiated may differ due to implementation differences).
Here's a trivial test program:
#include <stl.h> // include everything for simplicity. #include <iostream.h> #include <stdlib.h> int main(int, char*[]) { list<int> list1; for(int i = 0; i < 10; ++i) { list1.push_back(rand()); } return 0; }
(STL_INCLUDE_DIRS and STL_LIBS depend on your system).
% g++ -fno-implicit-templates -c $(STL_INCLUDE_DIRS) f1.cc % g++ -o f1 f1.o $(STL_LIBS) |& c++filt ld: Undefined symbol list<int>::list(void) list<int>::push_back(int const &) list<int>::~list(void) collect2: ld returned 2 exit status gmake: *** [f1] Error 1
So all we have to do is to instantiate the members of list<int> listed above. But the problem is that GCC doesn't allow instantiating individual members, so we'll simply instantiate ALL the members for now (until GCC fixes it).
so now I can create a file template-inst.cc:
#include <stl.h> template class list<int>;
% g++ -c $(STL_INCLUDE_DIRS) template-inst.c % g++ -o f1 f1.o template-inst.o $(STL_LIBS) |& c++filt % ./f1 %Note that using -fno-implicit-templates on the manual instantiation file will get you into lots of trouble. Try it and see. Something to do with some of the static indirect template functions that don't get instantiated indirectly when using -fno-implicit-templates flag.
Now for a bit of reality. Let's say you are going to use the following templated data types in STL:
and the following algorithms:
#include <stl.h> // include everything for simplicity. #include <iostream.h> #include <stdlib.h> int main(int, char*[]) { deque<int> deque1; for(int i = 0; i < 10; ++i) { deque1.push_back(rand()); } ostream_iterator<int> out(cout, " "); cout << "==> Deque1: "; copy(deque1.begin(), deque1.end(), out); cout << endl << endl; list<int> list1; copy(deque1.begin(), deque1.end(), back_insert_iterator<list<int> > (list1) ); cout << "==> List1: "; copy(list1.begin(), list1.end(), out); cout << endl << endl; // // This nested (within main) struct is going to cause problems later // on. // struct FooBar { void operator() (int val) const { cout << " " << val; } }; cout << "==> For_each List1: "; for_each(list1.begin(), list1.end(), FooBar()); cout << endl << endl; return 0; }
% g++ -fno-implicit-templates -c $(STL_INCLUDE_DIRS) f2.cc % g++ -o f2 f2.o $(STL_LIBS) |& c++filt ld: Undefined symbol list<int>::list(void) deque<int>::deque(void) deque<int>::begin(void) copy(list_iterator<int>, list_iterator<int>, ostream_iterator<int>) copy(deque_iterator<int>, deque_iterator<int>, ostream_iterator<int>) copy(deque_iterator<int>, deque_iterator<int>, back_insert_iterator<list<int> >) ostream_iterator<int>::ostream_iterator(ostream &, char *) deque<int>::end(void) for_each(list_iterator<int>, list_iterator<int>, main::FooBar) list<int>::~list(void) list<int>::begin(void) list<int>::end(void) back_insert_iterator<list<int> >::back_insert_iterator(list<int> &) deque<int>::~deque(void) deque<int>::push_back(int const &) collect2: ld returned 2 exit status gmake: *** [f2] Error 1
Note the for_each instantiation -- we cannot manually instantiate it because of the main::FooBar, so we simply move FooBar to file scope (not shown here) and redo the compile and link commands.
After moving FooBar to file scope in f2.cc:
% g++ -fno-implicit-templates -c $(STL_INCLUDE_DIRS) f2.cc % g++ -o f2 f2.o $(STL_LIBS) |& c++filt ld: Undefined symbol list<int>::list(void) deque<int>::deque(void) deque<int>::begin(void) copy(list_iterator<int>, list_iterator<int>, ostream_iterator<int>) copy(deque_iterator<int>, deque_iterator<int>, ostream_iterator<int>) copy(deque_iterator<int>, deque_iterator<int>, back_insert_iterator<list<int> >) ostream_iterator<int>::ostream_iterator(ostream &, char *) deque<int>::end(void) for_each(list_iterator<int>, list_iterator<int>, FooBar) list<int>::~list(void) list<int>::begin(void) list<int>::end(void) back_insert_iterator<list<int> >::back_insert_iterator(list<int> &) deque<int>::~deque(void) deque<int>::push_back(int const &) collect2: ld returned 2 exit status gmake: *** [f2] Error 1
#include <stl.h> #include <iostream.h> template class list<int>; template class list_iterator<int>; template class back_insert_iterator<list<int> >; template class ostream_iterator<int>; template class deque<int>; template class deque_iterator<int>; template void copy( list_iterator<int>, list_iterator<int>, ostream_iterator<int> ); template void copy( deque_iterator<int>, deque_iterator<int>, ostream_iterator<int> ); template void copy( deque_iterator<int>, deque_iterator<int>, back_insert_iterator<list<int> > ); struct FooBar { void operator() (int val) const { cout << " " << val; } }; template void for_each(list_iterator<int>, list_iterator<int>, FooBar);
% g++ -fno-implicit-templates -c $(STL_INCLUDE_DIRS) f2.cc % g++ -c $(STL_INCLUDE_DIRS) template-inst.cc % g++ -o f2 f2.o template-inst.o $(STL_LIBS) |& c++filt % ./f2 ==> Deque1: 1103527590 377401575 662824084 1147902781 2035015474 368800899 1508029952 486256185 1062517886 267834847 ==> List1: 1103527590 377401575 662824084 1147902781 2035015474 368800899 1508029952 486256185 1062517886 267834847 ==> For_each List1: 1103527590 377401575 662824084 1147902781 2035015474 368800899 1508029952 486256185 1062517886 267834847
Let's start with a trivial program, where a list is the only STL data type used, specifically list<int>. For the examples here, I'm assuming ObjectSpace STL<ToolKit> with GCC 2.7.0 appropriately patched with Jason Merrill template frepo patch. It should also work just fine with any other STL implementation that happens to work with GCC.
Here's a trivial test program:
#include <stl.h> // include everything for simplicity. #include <iostream.h> #include <stdlib.h> int main(int, char*[]) { list<int> list1; for(int i = 0; i < 10; ++i) { list1.push_back(rand()); } return 0; }
(STL_INCLUDE_DIRS and STL_LIBS depend on your system).
% g++ -frepo -c $(STL_INCLUDE_DIRS) f1.cc % g++ -frepo -o f1 f1.o $(STL_LIBS) collect: recompiling f1.cc collect: relinking collect: recompiling f1.cc collect: relinking collect: recompiling f1.cc collect: relinking %
And Voila! Granted, the first time you better go to lunch during build, but the subsequent times are much faster. How does it work? Quick answer: look in the build directory for f1.rpo and study it. You can get a good feel for what's happenning here by doing the following:
[ build all the object files needed to build the library using the -frepo option ] % gcc $(CPPFLAGS) $(CXXFLAGS) -frepo -o closure $(OBJS) collect: recompiling xx.cc collect: relinking ... collect: recompiling xx.cc collect: relinking [ Whole bunch of error messages about unresolved stuff -- ignore ] % ar crv libxyz.a $(OBJS) % ranlib libxyz.aHere the link step forces the instantiation of the templates used within the library and rebuilds the .o files. Now the clients have to provide the instantiation for only those templates that library does not directly use.
This forced instantiation is fortunately quite extendible; consider the following case:
The fix is quite simple:
% gcc $(CPPFLAGS) $(CXXFLAGS) -frepo -o closure $(L2_OBJS) libl1.a collect: recompiling xx.cc collect: relinking ... collect: recompiling xx.cc collect: relinking [ Whole bunch of error messages about unresolved stuff -- ignore ] % ar crv libl2.a $(L2_OBJS) % ranlib libl2.a
// // Suck in the entire (HP implementation derived) STL. // This is very helpful for beginners, and one gets more familiar with // STL, it's easy to figure out which headers to include. Also, different // implementations might have different header names, eg., ObjectSpace // has <algorith.h> and HP has <algo.h> Go figure. // #ifndef STL_H #define STL_H #include <algo.h> #include <function.h> #include <iterator.h> #include <list.h> #include <deque.h> #include <map.h> #include <pair.h> #include <set.h> #include <stack.h> #include <vector.h> #endif
C++ draft standard document:
Web pages/FTP sites with info on STL:
Back to index
Go to Mumit's Home Page