1 / 59

Embracing the C++ STL: Why Angle Brackets are Good for You Pete Isensee

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”

rane
Download Presentation

Embracing the C++ STL: Why Angle Brackets are Good for You Pete Isensee

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Embracing the C++ STL:Why Angle Brackets areGood for YouPete Isensee

  2. Introduction • STL Background & History • Key Concepts • Containers, Iterators and Algorithms • Efficiency and Thread Safety • Tips and Tricks

  3. 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

  4. Preliminaries • Microsoft Visual C++ 6.0 • Dinkumware STL implementation • Benchmarks on Pentium III - 550MHz • Performance graphs show relative performance – taller is better!

  5. 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

  6. Advantages • Standardized • Thin & efficient • Little inheritance; no virtual functions • Small; easy to learn • Flexible and extensible • Naturally open source

  7. Disadvantages • Template syntax • Difficult to read & decipher • Poor or incomplete compiler support • Code bloat potential • No constraints on template types • Limited container types

  8. 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

  9. 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)

  10. 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);

  11. 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);

  12. 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

  13. 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

  14. 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

  15. 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

  16. 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

  17. 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

  18. Map • Dictionary of sorted elements • List of sorted key and value pairs • Same implementation and characteristics of set container

  19. Container Adaptors • Example adapator code stack<int, deque<int> > TechnoTrousers; TechnoTrousers.push(1); int i = TechnoTrousers.top(); TechnoTrousers.pop();

  20. 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

  21. 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

  22. 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;

  23. 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(), ...)

  24. 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());

  25. 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);

  26. 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());

  27. Efficiency • Designed to be as fast as hand-coded routines • Limiting factor is typically copy ctor and assignment operator

  28. Efficiency (cont.) • STL faster in some cases than standard C functions const char* WestWallaby = “Gromit”; strchr(WestWallaby, ‘m’); find(WestWallaby, WestWallaby+6, ‘m’);

  29. 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

  30. 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

  31. 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;

  32. 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!

  33. 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); }

  34. 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

  35. 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

  36. 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

  37. 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)

  38. 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

  39. 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

  40. 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)

  41. 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);

  42. 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)); } };

  43. 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());

  44. 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!

  45. 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

  46. 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

  47. 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

  48. 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;

  49. 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);

  50. 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!

More Related