The Standard Template Library YAFGLP † Mostly based on Effective STL by Scott Meyers † Yet...

36
The Standard Template Library YAFGLP Mostly based on Effective STL by Scott Meyers Another Fantastic GLunch Presentation

Transcript of The Standard Template Library YAFGLP † Mostly based on Effective STL by Scott Meyers † Yet...

The Standard Template Library

YAFGLP†

Mostly based on Effective STL by Scott Meyers

†Yet Another Fantastic GLunch Presentation

About this presentation

● STL, Boost and the Standard● Generic programming and templates● STL components and their interactions● STL tricks and pitfalls

● I will not present a complete class catalog, that's what books are for.

● It will probably not convince the C diehards but, with luck, they might find one or two useful tools

STL implementations

● Several implementations of the STL exist, notably– HP – the original implementation– SGI – free, an 'upgrade' on HP's implementation

● Excellent STL reference at http://www.sgi.com/tech/stl

– STLPort – free, portable on many platforms● Answer to compatibility problems● Download at http://www.stlport.org (highly recommended)

– Dinkumware – which provides a 'light' version in VC++● The STL provided with VC++ 6 is faulty, some fixes are

available at http://www.dinkumware.com/vc_fixes.html

– And others...

The Boost library

● Implements many more tools and operations– regular expressions, threads, quaternions, Python

integration, static asserts, timers, smart pointers...

● Considered as 'best candidates for standardisation'– Under consideration : <cstdint>, type traits, regular

expressions, smart pointers, random numbers, rational numbers and threads

● Freely available at http://www.boost.org● I think it is full of cool stuff

Generic Programming

● STL uses generic programming– This is not object-oriented programming– No class hiearchy, no virtual dispatch– Templates are the enabling feature

● Based on 'concepts' instead of classes– If it looks like a duck... it is a duck– Type matters less than the operations implemented– Functions adapt to their arguments at compile time

OOP vs. Generic Programming

● struct Base{ virtual void Foo()=0;};

struct A : public Base{ void Foo(); };

struct B : public Base{ void Foo(); };

void Bar( Base& obj ){ obj.Foo(); }

● Foo is called through A or B's virtual function table pointer

● Run-time polymorphism

● struct A{ inline void Foo(); };

struct B{ inline void Foo(); };

template<typename T>void Bar( T& obj ){ obj.Foo(); }

● The compiler generates two distinct specialisations, void Bar(A&) and void Bar(B&) as needed, with the appropriate Foo member function inlined

● Compile-time polymorphism

OOP vs. Generic Programming

● With object-oriented programming– Actual parameter must derive from formal parameter– Must use virtual member, with associated overhead– Can delay the function choice until run-time.

● With generic programming – Parameter need only to implement the operations used– No run-time overhead, possible inlining– Type must be known at compile-time

● The two paradigms are orthogonal

A few words on templates

● A template parameter is a joker in the definition– Declare a type parameter with class or typename– Declare an integral parameter with bool, int ...– Any template parameter can have a default value

● Both classes and functions can be templated– Programmer selects class specialisations explicitly– Compiler fits an overloaded function to its arguments

● Specialisations can be explicitly implemented– Different behaviour based on template parameter– Handle special cases, policies...

Partial Template Specialisation

● Class examples, would be similar with functions– template<class T> class Foo {...};template<class T> class Foo<T*> {...};

– template<class T, class U> class Bar {...};template<class T> class<T, void> Bar {...};

– template<class T, bool b> class Quux {...};template<class T> class Quux<T, true> {...};

● Well... Visual C++ does not support it.– MS claims it will reach compliance by the end of the year– Workarounds exist (check with me if you're curious)– g++ works fine

Formal type exports and trait classes

● Formal type export– Classes can have public typedefstruct A { typedef double ResultType; ... };struct B { typedef char ResultType; ... };

– Templates use the typename keyword to access themtemplate<class T> typename T::ResultType Foo( T& arg );

● Trait class– Exports info on another type, often a template parameter– Thus std::numeric_limits<long> holds info on long

● Allow specialisations to go beyond class boundaries– specialise on exported type, trait or use default parameters

STL components overview

● Data storage, data access and algorithms are separated– Containers hold data– Iterators access data– Algorithms, function

objects manipulate data– Allocators... allocate

data (mostly, we ignore them)

Container Container

Algorithm

Container

Sequence Containers

– vector<T> – dynamic array● Offers random access, back insertion● Should be your default choice, but choose wisely● Backward compatible with C : &v[0] points to the first element

– deque<T> – double-ended queue (usually array of arrays)● Offers random access, back and front insertion● Slower than vectors, no C compatibility

– list<T> – 'traditional' doubly linked list● Don't expect random access, you can insert anywhere though

– string – yes, it is a STL container (a typedef actually)– Nonstandard containers : slist, rope...

Associative Containers

– Offer O(log n) insertion, suppression and access– Store only weakly strict ordered types (eg. numeric types)

● Must have operator<() and operator==() definedand !(a<b) && !(b<a) ≡ (a==b)

– The sorting criterion is also a template parameter– set<T> – the item stored act as key, no duplicates– multiset<T> – set allowing duplicate items– map<K,V> – separate key and value, no duplicates – multimap<K,V> – map allowing duplicate keys– hashed associative containers may be available

● Dinkumware and SGI did things differently though

Container Adaptors

● There are a few classes acting as wrappers around other containers, adapting them to a specific interface

– stack – ordinary LIFO– queue – single-ended FIFO– priority_queue – the sorting criterion can be specified– Programmers can specify the underlying data type– usually a deque

Tip : vector<bool>

● Meyers: "As an STL container, there are really only two things wrong with vector<bool>. First it's not an STL containers. Second it doesn't hold bools. Other than that, there's not much to object to." (Effective STL, p79)

– vector<bool> does not conform to STL requirements– it stores bools in a packed representation (e.g. bitfield)– Accessing it returns proxy objects to bools, not true bools– Use a deque<bool> or a bitset to store bools– You won't get C compatibility (but C doesn't have bools

anyways)

Tip : reserve()

● Inserting elements may cause a memory reallocation– container is doubled in capacity– elements are copied over

● Both string and vector let you increase capacity in advance through reserve()– May eliminate unnecessary reallocations– Keeps safety of dynamic data structures over C arrays

● v.reserve(n)allocates memory for n objects● Distinguish capacity (memory) and size (objects)

– Writing beyond the size of the container is still bad

Tip : size() and empty()

● You may check whether a container is empty by writing c.size() == 0 or c.empty()

● However, with lists, which have a splice() function, if splice() is O(1), size() must be O(n) and conversely.

● Therefore, while empty() will always run in O(1), size() may not. You should thus prefer calling empty() to checking size() against zero.

Ranges

● Most operations are done on semi-open ranges

● STL Containers all have begin() and end() members– end() points one-past-the-end of the container– In arrays, base+size points one-past-the end of the array– loop termination test is iterator!=container.end()

● Such a range holds exactly last-first elements– It is empty if first==last (cf. test for empty())

first last

Tip : assign()

● c.assign(first,last) copies the data in [first,last) to c– Will discard the data c may have had stored– Works regardless of the natures of c and the range– Faster than manually looping through the elements

● Containers have a range constructor c(first,last)– Will initialize the container with the data from the range

● In general, prefer ranged to single-element operations● Remember that first is in the range but last is not.

Tip : swap()

● swap(c1,c2) where c1 and c2 are of the same type– Will exchange the contents of c1 and c2

● Can be used to trim down the memory reserved– Initialise an empty container with the elements of c1– swap its contents with c1– The container will only keep the minimum needed– vector<T> v1(1000); // 1000 elementsvector<T>(v1.begin(), v1.end()).swap(v1);

Iterators

● Abstract and streamline access to the containers– Are exposed as formal types by the container– Present containers as ranges, with pointer semantics– Hide implementation details of data access and iteration

● Several categories of iterators, based on capabilities– Output – can write to them, ++ may be a no-op– Input – can read from them, ++ may be a no-op– Forward – Input, can use ++– Bidirectional – Forward, can use --– Random access – Bidirectional, any increment

Containers and iterators categories

– vector – Random access iterator– deque – Random access iterator– list – Bidirectional iterator– string – Random iterator– set, multiset – Bidirectional iterator, data is constant– map, multimap – Bidirectional iterator, key is constant– Hashed containers – Bidirectional iterators

Iterator adaptors

● Modify the behavior of iterators and thus, algorithms– reverse_iterator(it) – reverses iteration direction

● containers provide rbegin() and rend()● base() points to the original iterator. ● There is a one-element offset to preserve range semantics.

– back_inserter(c), front_inserter(c), inserter(c,it)● assigning inserts in the container instead of overwriting

– istream_iterator<T>(istream) – reads from a stream● end-of-stream / range iterator is istream_iterator<T>()

– ostream_iterator<T>(ostream, sep) – writes to a stream● assigned data is written to the stream, separated by sep

Tip: Reading a stream

● You can't transfer one stream to another, but you can transfer the input buffer :

– ifstream ifs( "input" );ofstream ofs( "output" );

– ofs << ifs.rdbuf(); // file copy

● You can also directly load the stream into a container– vector<char> v1( istream_iterator<char>( ifs ), // high-level istream_iterator() );

– vector<char> v2( istreambuf_iterator<char>( ifs ), // low-level istreambuf_iterator() );

Algorithms

● Implement simple, or not-so-simple loops on ranges– copy, find, but also partition, sort, next-permutation

● Specify their need in terms of iterator categories– They do not care about the exact class– Must pay attention to the iterators provided by containers

● Often exist in several versions– One uses default comparison, user-defined value– Other calls user-provided predicate, function

● Some impose requirement on the data– binary_search needs sorted data

Tip: Algorithms vs. member functions

● Algorithms are simple, generic● template<class InpItor, class UnaryFunc> inline

UnaryFunction for_each(InpItor First, InpItor Last, UnaryFunc Func){

for (; First != Last; ++First) Func(*First);return (Func);

}

– They know nothing about the containers they work on– Specialised algorithms may have better performance– Those algorithms are implemented as member functions

● Use member functions when they exist– eg. list::sort() vs. sort()

Tip : erase() and remove()

● remove() doesn't remove elements from containers– It overwrites their value with ones located further away– Leaves extra elements at the end of the container– Return value is the first element to erase

● container::erase() actually purges the elements● c.erase( remove(c.begin(),c.end(), Pred), c.end() );

● Caveat: Pointers have no destructors !– remove() causes memory leaks on containers of pointers– Use smart pointers like boost::shared_ptr instead– std::auto_ptr cannot be stored in containers

Tip : copy_if()

● The STL has a lot of 'copy' algorithms– copy, copy_backward, replace_copy, reverse_copy,

replace_copy_if, unique_copy, remove_copy, rotate_copy, remove_copy_if, partial_sort_copy, uninitialized_copy

● But copy_if is conspicuously missing...template<class InpIt, class OutIt, class Predicate>OutIt copy_if( InpIt begin, InpIt end, OutIt destBegin, Predicate p){ while (begin != end) { if (p(*begin)) *destBegin++ = *begin; ++begin; } return destBegin;}

Function objects (Functors) (1)

● It is an object which implements operator()● If it looks like a function call, it is a function call● The for_each template, 'calls' Func(*First)

– Either Func is a function pointer– Or Func is an object implementing operator()

● STL functors export argument and return types– Inherit from unary_function or binary_function– Make it much easier to combine them

Function objects (Functors) (2)

● Defined function objects include– less – calls operator< used in sort, to order map keys...– compose – function composition

● The actual function implemented can be inlined– Passing a functor avoids calling through a pointer– Inlining within the algorithm allows more optimisations– That's how C++ sort() can beat C qsort()

● Functions objects can have member data– like static local variables, but can have one by object– Can pass parameters to constructor and to operator()

Sample Functor implementation

● template<class T>class MyFunctor : public unary_function(void, T&){ const int i;public: MyFunctor( int i ) : i_( i ) {}; void operator()( T& arg ) { arg += i; };};

● Inheriting from unary_function makes use much easier (do not have to specify the types again).

● 'small' template problem : references to references are not allowed. This is an acknowledged defect in the standard that will be corrected.

Functor adapters

● mem_fun– encapsulate a member function working on a pointer

● mem_fun_ref– encapsulate a member function working on a reference

● ptr_func– encapsulate a function pointers

● bind1st, bind2nd– fix the first or second parameter

Useless Example● list<float> l(100);

generate_n(l.begin(), 100, rand); // no ()cout << accumulate( l.begin(), l.end(), 1.0, multiplies<float>() )

<< endl;

vector<float> v(l.begin(), l.end());

sort(v.begin(), v.end(), greater<float>() );v.erase( unique(v.begin(), v.end()), v.end() );transform(v.begin(),v.end(), v.begin(), bind2nd(divides<float>, 3.0));cout << count(v.begin(), v.end(), bind2nd( less<float>, 1000.0 ) );

ofstream ofs( "data.txt" );copy( v.begin(), v.end(), ostream_iterator<float>( ofs, ' ' );

C++ STL Books

● Effective STL – S. Meyers, ed. A-W

● The C++ Standard Library – N.M. Josuttis, ed. A-W

● STL Tutorial and Reference Guide (now in 2nd ed)– D.P. Musser & A. Saini, ed. A-W

● The C++ Programming Language 3rd ed– B. Stroustrup, ed. A-W

WWW Links

● C/C++ User Journal – 'C++ Experts forum'http://www.cuj.com/experts/author_index.htm?topic=experts

● Association of C/C++ Usershttp://www.accu.org

● STL tutorialshttp://www.cs.rpi.edu/projects/STL/htdocs/stl.html

http://www.nanotech.wisc.edu/~khan/software/stl/STL.newbie.html

or just hit Google with "STL Tutorial"

Library Links

● Visual C++ 6 partial library fixes

http://www.dinkumware.com/vc_fixes.html

● SGI's library and documentation

http://www.sgi.com/tech/stl

● STLport libary, free portable implementation

http://www.stlport.org

● Boost library, extension to the STL

http://www.boost.org