590 likes | 670 Views
Embracing the C++ STL: Why Angle Brackets are Good for You Pete Isensee. Introduction. STL Background & History Key Concepts Containers, Iterators and Algorithms Efficiency and Thread Safety Tips and Tricks. Goals. STL Newbie Convince you that it’s a “good thing”
E N D
Embracing the C++ STL:Why Angle Brackets areGood for YouPete Isensee
Introduction • STL Background & History • Key Concepts • Containers, Iterators and Algorithms • Efficiency and Thread Safety • Tips and Tricks
Goals • STL Newbie • Convince you that it’s a “good thing” • Help you avoid common mistakes • STL Junkie • Add new STL techniques to your toolbox • Tricks and tips
Preliminaries • Microsoft Visual C++ 6.0 • Dinkumware STL implementation • Benchmarks on Pentium III - 550MHz • Performance graphs show relative performance – taller is better!
History • Alex Stepanov & Meng Lee, based on earlier work by Stepanov & Musser • Proposed to C++ committee late ‘93 • HP version released ‘94 • Accepted into Standard summer ‘94 • Standard froze ‘97, ratified ‘98
Advantages • Standardized • Thin & efficient • Little inheritance; no virtual functions • Small; easy to learn • Flexible and extensible • Naturally open source
Disadvantages • Template syntax • Difficult to read & decipher • Poor or incomplete compiler support • Code bloat potential • No constraints on template types • Limited container types
Key Concepts • Generic algorithms • Container classes • Iterators : “container walkers” for accessing container elements • Iterators provide an abstraction of container access, which in turn allows for generic algorithms • Iterator invalidation
Key Concepts (cont.) • Ranges: C/C++ “past-the-end” pointer T Wallace[N]; T* p = (Wallace + N); // valid pointer T w = *(Wallace + N); // invalid dereference c.begin() == (Wallace); // first element c.end() == (Wallace + N); // valid iterator *c.end(); // invalid dereference • end() - begin() = size() • if (begin() == end()) container is empty • for (iter i = begin(); i != end() ++i)
Key Concepts (cont.) • Linear search example template <class InputItr, class T> InputItr find(InputItr bg, InputItr end, const T& val) { while (bg != end && *bg != val) ++bg; return (bg); } const int nSize = 4; int Gromit[nSize] = { 5, 18, 23, 9 }; int* pFind = find(Gromit, Gromit + nSize, 23); vector<int> Preston; vector<int>::iterator i = find(Preston.begin(), Preston.end(), 4);
Putting the STL into Action • Include files have no “.h” • Standard namespace #include <cstdio> // new include method #include <vector> // vector container #include <algorithm> // STL algorithms using namespace std; // assume std:: vector<int> Chuck; // declare a growable array Chuck.push_back(1); // add an element find(Chuck.begin(), Chuck.end(), 1);
Containers • Containers contain elements; they “own” the objects • Containers provide iterators that point to its elements. • Containers provide a minimal set of operations for manipulating elements
Containers (cont.) • Minimum container object requirements X() // default ctor X(const X&) // copy ctor X& operator = (const X&) // assignment op bool operator < (const X&) // comparison op bool operator == (const X&) // comparison op
Vector • Dynamic array • Fast ins/erase from end of vector • reserve(), capacity() • Contiguous block of memory • Obliged to grow by some factor (2x) when size() exceeds capacity() • Insert invals all iters if capacity change; insert/erase invals all iters following
Deque • Double-ended queue (“deck”) • Fast ins/erase at begin and end • Directory array of pointers to nodes, where each node is small array of T • Insert invals all iters; erase in middle invals all; erase at begin/end on invals iter to begin/end
List • Doubly-linked list • Fast insert/erase; no random access • Special functions: splice(), merge() • Erase invals iters to erased elements; insert never invals any iters
Set • List of sorted elements • Fast retrieval based on key (log N) • Fast insert/erase (log N) • Red-black tree (balanced 2-3-4 tree) • Erase invals erased elems; insert never invals any iters
Map • Dictionary of sorted elements • List of sorted key and value pairs • Same implementation and characteristics of set container
Container Adaptors • Example adapator code stack<int, deque<int> > TechnoTrousers; TechnoTrousers.push(1); int i = TechnoTrousers.top(); TechnoTrousers.pop();
What’s Missing? • stack-based arrays (T a[N]) • hash tables • singly-linked lists • some STL implementations include one or more of these “non-standard” containers
Container Efficiency • Overhead is approx. per-element size in bytes • Hash and slist containers included for comparison only. C/N indicates best/worst case times
Iterators • Typical iteration c<T>::iterator i; for (i = c.begin(); i != c.end() ++i) // forward T t = *i; for (i = c.rbegin(); i != c.rend() ++i) // backward T t = *i;
Algorithms • Approx. 60 standard algorithms • searching (e.g. find()) • sorting (e.g. sort()) • mutating (e.g. transform()) • numerical (e.g. accumulate()) • Most functions take the form: • fn(c.begin(), c.end(), ...)
Algorithms (cont.) • Examples: #include <algorithm> // return num elements equal to 123 int i = count(c.begin(), c.end(), 123); // negate all elements transform(c.begin(), c.end(), negate<int>()); // print all elements for_each(c.begin(), c.end(), print<int>()); // shuffle the deck random_shuffle(deck.begin(), deck.end());
Function Objects (Functors) • C++ objects that can be called like a function to implement “callbacks” • Use C++ operator()(...) • Simplest type is a function pointer bool StlStrComp(const char* a, const char* b) { return (strcmp(a, b) == -1); } vector<char*> v; sort(v.begin(), v.end(), StlStrComp);
Functors (cont.) • Functors that do ordering are called “predicates” struct StlStrPred // “public” class { bool operator()(const char* a, const char* b) { return (strcmp(a, b) == -1); } }; vector<char*> v; sort(v.begin(), v.end(), StlStrPred());
Efficiency • Designed to be as fast as hand-coded routines • Limiting factor is typically copy ctor and assignment operator
Efficiency (cont.) • STL faster in some cases than standard C functions const char* WestWallaby = “Gromit”; strchr(WestWallaby, ‘m’); find(WestWallaby, WestWallaby+6, ‘m’);
Efficiency (cont.) • Sorting (ints) int arr[nElements]; qsort(arr, nElements, sizeof(int), IntComp); int arr[nElements]; sort(arr, arr + nElements); // STL int array sort vector<int> v; sort(v.begin(), v.end()); // STL int vector sort
Efficiency (cont.) • Sorting (strings) char* arr[nElements]; qsort(arr, nElements, sizeof(char*), StrComp); sort(arr, arr + nElements, StlStrComp); sort(v.begin(), v.end(), StlStrComp); // char* sort(v.begin(), v.end(), StlStrComp); // string
STL Allocators • Every STL container takes an allocator object as a template parameter template <class T> public AllocSpecialCheese { public: pointer allocate(size_type, const void*); void deallocate(void*, size_type); // ... other boilerplate code here }; set<int> Camembert; // default allocator // All Wensleydale allocations use special allocator set<int, AllocSpecialCheese> Wensleydale;
Template Partial Specialization // generic template function for swapping objects template <class T> void swap(T& x, T& y) { T z(x); x = y; y = z; } swap(v1, v2); // swapping vectors: slow! v1.swap(v2); // swapping vectors: fast! // template partial specialization template <class T> void swap(vector<T>& x, vector<T>& y) { x.swap(y); } swap(v1, v2); // fast!
Template Specialization part II // STL generic copy() algorithm template<class InItr, class OutItr> OutItr copy(InItr bg, InItr end, OutItr val) { for (; bg != end; ++val, ++bg) *val = *bg; return (val); } // A fast version for simple memory chunks template<> char* copy(const char* bg, const char* end, char* val) { size_t n = end - bg; memcpy(val, bg, n); return (val + n); }
Thread Safety • Official answer: STL has no thread safety obligations whatsoever • One (bad) answer: have STL handle all synchronization issues • Typical answer: make STL thread safe internally, but require users to insure no thread accesses a container when another thread modifies the container
Thread Safety (cont.) • Current status of implementations • Original HP version not thread safe • SGI thread safe • VC mostly thread safe(dinkumware.com/vc_fixes.html) • Typical STL implementation promises • Multiple reads is thread safe • Read/write across different containers, same objects, is thread safe • Writes to a container by one thread and read/writes by another thread is not thread safe; users must prevent
Exception Safety • C++ standard requires the following • destructors may not throw excptns • valid iterator operations may not throw • containers must “survive” excptns; content unspecified, but still destructable • an excptn thrown while inserting one element leaves the container unchanged • an excptn thrown while inserting two+ elements leaves a list unchanged
Code Bloat • Templates expand into different sets of code for each type T • If different types have the same size and same comparison functions, the compiler can optimize • Some STL implementations are optimized to minimize code bloat (XTL from DeltaLogic)
Compiler Warnings & Errors • VC warning C4786: identifier truncation #pragma warning(disable: 4786) // before headers! • Errors/warnings in header files • really means: your code has a problem • Interpreting gonzo error messages map<string, int> m; map<string, int>const_iterator i = m.find(“abc”); m.erase(i); // “big” error here
Compiler Errors (cont.) error C2664: 'class std::_Tree<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,int>,struct std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::_Kfn,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::iterator __thiscall std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::erase(class std::_Tree<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,int>,struct std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::_Kfn,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::iterator)' : cannot convert parameter 1 from 'class std::_Tree<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,int>,struct std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::_Kfn,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::const_iterator' to 'class std::_Tree<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,int>,struct std::map<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::_Kfn,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > >,class std::allocator<int> >::iterator' No constructor could take the source type, or constructor overload resolution was ambiguous
Extending the STL • Designed for extension • Not difficult to write new algorithms, containers, or iterators • SGI implementation has many useful container extensions (hash tables, slist)
Our own Algorithm // Compute sum of all squares in range template<class InItr, class T> T sumsqrs(InItr bg, InItr end, T init) { T ss(init); for (; bg != end; ++bg) { ss += (*bg) * (*bg); } return (ss); } int CloseShave[4] = { 1, 2, 3, 4 }; int x = sumsqrs(CloseShave, CloseShave+4, 0); // 30 deque<double> WrongTrousers; // 1.1, 2.2, 3.3 double y = sumsqrs(WrongTrousers.begin(), // 16.94 WrongTrousers.end(), 0.0);
Our own Iterator template <class C, class T, class A = allocator<T> > struct MfcIt : public _Ranit<T, A::difference_type> { C* mpC; // MFC container int mI; // index into container MfcIt(C* pC = 0) : mpC(pC), mI(0) {} MfcIt(C* pC, int n) : mpC(pC), mI(n) {} MfcIt begin() { return (MfcIt(mpC, 0)); } MfcIt end() { return (MfcIt(mpC, mpC->GetSize())); } T& operator * () const { return ((*mpC)[mI]); } MfcIt& operator ++ () { if (mI < mpC->GetSize()) ++mI; else mI = 0; return (*this); } MfcIt operator + (difference_type n) const { MfcIt tmp = *this; return (tmp += n); } bool operator == (const MfcIt& i) const { return ((mI == i.mI) && (mpC == i.mpC)); } bool operator < (const MfcIt& i) const { return ((mI < i.mI) && (mpC == i.mpC)); } };
Our own Iterator (cont.) • Example of using the MFC/STL iterator // MFC container CStringArray arr; arr.Add("xyz"); arr.Add("abc"); // Create STL iterator from MFC container MfcIt<CStringArray, CString> mfc(&arr); // Sort the MFC container using an STL algorithm! sort(mfc.begin(), mfc.end());
Common Mistakes • Template of templates stack<vector<int>> GoneWrong; // need space: “> >” • Same algorithm, different container sort(Marmalade.begin(), Jelly.end()) // crash! • Right iterator, wrong container list<int>::iterator i = Feathers.begin(); McGraw.erase(i); // i doesn’t point into McGraw!
Common Mistakes (cont.) • Invalid iterator vector<int>::iterator i = GrandDayOut.begin(); GrandDayOut.push_back(1); // potential realloc TheCooker(*i); // uh-oh! i might be invalid • Adding elements with subscript op vector<char> Wendolene; Wendolene[0] = ‘W’; // crash! Wendolene.push_back(‘W’); // the right way
Common Mistakes (cont.) • Sorting problems • e.g. lookups fail, a set gets duplicates • Usually a problem with op < or op == • Rules • if x<y is true, y>x is always false • if x<y && y<z are true, x<z is always false • if x==y, then x<y is always false • if !(x<y) and !(y<x), x == y
Hiding the Angle Brackets • Not pretty // This is hard to read map<string, int> Shaun; Shaun.insert(pair<string, int>(“abc”, 31)); map<string, int>::iterator i = Shaun.find(“abc”); pair<string, int> pr = *i; pr.first; // “abc” pr.second; // int
Hiding the Angle Brackets (cont.) • Typedefs are your friend // Tuck these away in a header file typedef map<string, int> ShaunMap; typedef pair<string, int> ShaunPair; typedef ShaunMap::iterator ShaunItr; // Same code, but no more angle brackets ShaunMap Shaun; Shaun.insert(ShaunPair(“abc”, 31)); ShaunItr i = Shaun.find(“abc”); ShaunPair pr = *i;
Storing Pointers in Containers • Container will not delete the pointers when the container goes away • Will sort on pointer, not what it contains, unless you tell it otherwise deque<int*> Walkies; // sorted by pointer sort(Walkies.begin(), Walkies.end()); bool intpLess(const int* a, const int* j) { return (*a < *b); } // sorted by int sort(Walkies.begin(), Walkies.end(), intpLess);
Vector Tips • Use reserve() to set aside space when vector size is known in advance • Can I safely take the address of a vector element, e.g. &v[21]? • According to Standard, no. According to practice, yes. Standard expected to adjust. • Trimming unused space v.swap(vector<T>(v)); // vector, swap thyself!