Introduction to C/C++ Templates Ben Morgan

advertisement

Introduction to C/C++ Templates

Ben Morgan

Overview

• Object Orientation Recap

• Templates and Static

Polymorphism

• Introducing the STL

• Containers

• Iterators

• Associative Containers

• Algorithms

Recap : Object Orientation in C++ 1

• Last week, we introduced the concept of objects and OO programming which switches the focus from a ‘top-to-bottom’ procedural view to that of thinking in terms of creation and manipulation of objects with well defined interfaces

• Followed this through from structs, through opaque pointers to full classes, where we covered Constructors, Destructors and Operator Overloading.

main()

v

L v

boost_z() interval()

main() and FourVector are decoupled

Object (instance of

FourVector) handles anything to do with itself

Interaction via messages

Recap : Object Orientation in C++ 2

• Through the homework, you’ll have covered how to design and implement a

Particle class using the benefits of OO:

• Appropriate member variables that are not over-defined (e.g. mass, energy, momentum)

• Included a sensible interface with operator overloading where appropriate

• Understanding that a Particle class ‘has-a’ FourVector object

• A FourVector object ‘has-a’ ThreeVector object

• This should hopefully have illustrated the benefits of OO and how it can allow simplification and potential reuse of code.

C++ Templates

Introducing Static Polymorphism (or Generic Programming) in C++

Revisiting the swap function

• Let’s look again at the function we wrote back in Day 2 to swap the values of two double type variables: void swap( double & a, double & b) { double tmp(a);

a = b;

b = tmp;

}

• What if we want to swap the values of two variables that are of a di ff erent type to double ?

Spot the difference...

• Does this mean we need to implement swap for every type?

void swap( double & a, double & b) { double tmp(a);

a = b;

b = tmp;

} void swap( int & a, int & b) { int tmp(a);

a = b;

b = tmp;

}

// etc for float, Particle, etc..

Concepts

• What does swap actually do?

void swap( Foo & a, Foo & b) {

Foo tmp(a);

a = b;

b = tmp;

}

Take two Foo s by reference

Copy Construct a variable of Type Foo from Foo instance ‘a’

Assign a Foo variable from another Foo instance

Concepts, Types and Classes

• We can see that swap can be implemented for any Type that is:

• CopyConstructible , i.e. can be constructed from another Type instance

• Assignable , i.e. values can be assigned to a variable of type Type.

• Our own classes are Types, so these would need to define

• Copy constructor: Foo::Foo(const Foo&)

• Copy assignment operator: Foo& Foo::operator=(const Foo&)

• What we need is a mechanism to parameterize the Type with with which swap is called.

Introducing Templates : A swap Template

• In C++, we can parameterize on type using templates : template < typename T> void swap( T & a, T & b) {

T tmp(a);

a = b;

b = tmp;

}

• This function template definition specifies a family of functions that swap the values of two variables.

• The type of these variables is specified by the template parameter T, which must provide the operations used in the function.

Defining Templates

• A template (function, class, class method) is declared with the template keyword: template <comma separated list of parameters>

...definition...

• Type parameters are declared with the typename keyword followed by the parameter name: template < typename FooType, typename BarType>

...definition...

• You can also use the class keyword in place of typename , but typename is preferred to avoid confusion.

Using Template Functions

• After defining the template function, we can just call it directly: double a(5.7), b(46.6); swap(a, b);

• The compiler can deduce from this call that the template parameter type is double.

• The template parameter can also be specified explicitly via angle brackets: double a(5.7), b(46.6); swap< double >(a, b);

• This notation has to be used if the compiler cannot deduce the template parameter from the arguments.

Static Polymorphism

• The polymorphism in templates arises from the ability of one entity (function, class, class method) to be used with many di ff erent types

• Provided the Type implements the Concepts which the entity relies on

• However, the concrete behaviour is hard-coded at compile time because we or the compiler make an explicit type selection: swap< double >(a, b);

• No way to change this statement, and hence the Type, at runtime .

• Runtime Type selection requires dynamic polymorphism (Day 5!)

Templates and Headers

• With non-template functions, we’ve been able to put the function declarations in a header file ( .h,.hpp,.hh

), and the definitions in a source file ( .cc,.cpp,.cxx

)

• This doesn’t work with templates

• The problem is that a template doesn’t really exist until it’s instantiated , that is, called with a specific type: swap< double >(a, b);

• If the function definition is in a source file, the linker won’t find swap<double>

• Whilst there are ways around this, best and easiest thing is to put both definition and declaration in a header file.

Exercise 1: FileReader

• Take the FileReader class we’ve been using to read text files and:

1.Look at the getFieldAsXXX methods

2.Implement a template method getField

3.Use this method to read in the data in the supplied pdg.dat

database

4.Output the data to screen for now to check items are read correctly

Introducing the STL: Containers

Using Objects in the Standard Template Library

Containers

• In Day 2, we used C-style arrays to store collections of data (energies, momenta etc)

• We discovered and discussed the main disadvantages of arrays:

• They are not bounds checked

• You cannot determine the number of elements in a given array

• You cannot resize them dynamically without new/copy/delete.

• Whilst we can design our interfaces to work around these issues, C-style arrays are suboptimal for storing a collection of data.

The Standard Template Library

• C++ provides the so-called STL, which provides a wide range of tools for working with collections of data in an easy and robust manner.

• It makes extensive use of templates and generic programming, and provides:

Containers

Collections of Data

Iterators

Container Traversal

Algorithms

Operations over

Collections

Functors

Callable Objects

Containers - std::vector

• The simplest container - essentially a “smart” array.

• To use it, you need to include its header:

#include <vector>

• Like other containers, std::vector is a Template Class, with the Template

Parameter indicating the type of things it will store: std::vector< int > foo; //Stores ints std::vector<Bar> baz; //Stores Bars

• Vectors can only store types that are CopyAssignable and CopyConstructible

(C++11 changes this slightly because of std::move)

Containers - std::vector

• Default constructor of std::vector instantiates an empty container.

• To add elements, we use its push_back method: foo.push_back(4); int ans(42); foo.push_back(ans);

• Unlike an array, we can find out how many elements are stored using vector’s size method: size_t nElements = foo.size();

Containers -

std

::vector

• We can access specific elements of a vector using subscript operator as for arrays: foo[2] = 5; int res = foo[0];

• As with arrays, subscript access isn’t bounds checked...

• Use at method for bounds-checked access: foo.at(2) = 5; int res = foo.at(0);

• A std::out_of_range exception is thrown if you attempt to access an element that is out of bounds.

STL Documentation

• The STL is a fairly extensive library, so it’s worth spending some time browing the documentation.

• Many good online guides

• Really only two main textbooks!

• As before, you should prefer the C++11 versions of books.

Exercise 2: std::vector

• Modify the pdg.dat

file reader code from Exercise 1 to

1.Read each column of data into a std::vector holding the appropriate type

2.Loop over the contents of the vectors, printing out the size and contents to screen.

Iterators

• A more powerful and generic mechanism for accessing container contents

• Each container in the STL defines an Iterator object

• An Iterator points at an element in the container, and knows how to move from one element to the next.

• There are Concepts for Iterators, just as for Types

• For example, some can move backwards, skip elements etc.

• Dereferencing an iterator accesses the element it points to (like pointers)

• Containers also provide special iterators marking the container end.

Iteration - std::vector::iterator

std::vector< int > foo;

// fill foo somehow (not shown)...

// Initialize an iterator pointing at first element std::vector< int >::iterator iter = foo.begin();

// Obtain special “one past the end” iterator std::vector< int >::iterator fooEnd = foo.end();

// Iterate...

for ( ; iter != fooEnd; ++iter) {

// dereference to get value

std::cout << *iter << std::endl;

}

The STL with C++11 - auto

• Typenames for STL objects and others can get extremely long, impacting clarity of purpose

• C++11 introduces a very useful feature to help: the auto keyword.

• The auto keyword can be used to specify that the type of a variable is that of its initializer, e.g.

// C++98 std::vector< int >::iterator iter = foo.begin();

// C++11 auto iter = foo.begin();

Iteration in C++11 - range-based for

• The for-loop iteration we saw requires a fair amount of boiler plate; get the iterators, setup the range to loop over. C++11 introduces range-based loops, so we can do

// C++11 std::vector< int > foo;

... fill foo somehow...

for ( int i : foo) {

std::cout << i << std::endl;

}

// ...auto means we can also do...

for ( auto j : foo) {

std::cout << j << std::endl;

}

Exercise 3: std::vector::iterator

• Modify your loop(s) from Exercise 2 to use iterators.

Containers - std::map

• Apart from Sequence Containers like std::vector , the STL also provides

Associative Containers like std::map

• These also store Values , but now each value is associated with a Key

• This allows fast retrieval of elements based on the Key

• As with vectors, elements can be added to associative array, and iterators can be used to access the elements.

• If you’re familiar with Python, STL maps are similar to Python’s dictionary.

Containers - std::map

// Include the map header

#include <map> typedef allows you to create an alias to a type

Very useful for STL!

// use a typedef to simplify code!!

typedef std::map<std::string, int > PartItCont;

// create instance of map, which will be empty

PartIdCont particleIds;

// use insert and make_pair to add an element particleIds.insert(std::make_pair(“e-”, 11));

// can also use subscript notation to insert particleIds[“pi+”] = 211 ;

Iterators - std::map

// Search for a key using find std::string name( “e-” );

PartIdCont::iterator p = particleIds.find(name);

// If key isn’t found, iterator will point to end if (p != particleIds.end()) {

// Dereferencing iterator gets us a std::pair!

std::cout << “name: “ << (*p).first //key

<< “pdg : “ << (*p).second //value

<< std::endl;

}

// Can also iterate over all key/value pairs just as

// we did for std::vector, but again iterators point

// to std::pairs!

Exercise 4: class ParticleInfo

• We’ll now use the STL to create a class to provide a mini database of information on particles

1.Names

2.PDG codes

3.Charges

4.Masses

You’ve got the basic data in pdg.dat

, but first let’s think about the interface we want ParticleInfo to provide

Designing ParticleInfo

• The minimal interface ParticleInfo should have is as follows:

+ getPDGCode(std::string&) : int {query}

+ getCharge(int) : int {query}

+ getName(int) : std::string {query}

+ getMassMeV(int) : double {query}

+ getMassGeV(int) : double {query}

+ ParticleInfo(std::string&)

- ParticleInfo(const ParticleInfo&)

- operator=(const ParticleInfo&) : ParticleInfo&

• Implement ParticleInfo with the above interface and the data in pdg.dat

to populate the underlying database

• Are the any other methods ParticleInfo should have?

Algorithms

• Our use of iterators so far will have seemed a rather abstract way to write loops.

• The real power of iterators comes when combined with the third component of the STL: Algorithms

• Algorithms operate on a Range of elements defined by Iterators , performing some operation on the values in that Range

• Algorithms may modify the range (in other words the collection), or just perform some calculation.

• There are a large number of algorithms (and C++11 adds even more), so we’ll only consider a couple of simple examples

Algorithms - std::copy

• We’ve done a lot of output of the contents of vectors today using: std::vector< int > foo; std::vector< int >::iterator iter = foo.begin(); std::vector< int >::iterator fooEnd = foo.end(); for ( ; iter != fooEnd; ++iter) {

std::cout << *iter << std::endl;

}

• With the std::copy algorithm, we can reduce this to a single statement: std::copy(foo.begin(), foo.end(),

std::ostream_iterator< int >(std::cout, “\n” ));

Algorithms - Breakdown of std::copy

First two arguments provide Iterators that specify the Range the algorithm should operate on std::copy(foo.begin(), foo.end(),

std::ostream_iterator< int >(std::cout, “\n” ));

Third argument gives the start of the Range where the input Range will be copied to

• std::ostream_iterator is a specialized iterator that formats input to a supplied ostream .

• In this case it’s just std::cout , plus a newline after each element

Prefer Algorithms?

• Scott Meyers, in E ff ective STL , gives three reasons to prefer algorithms over handwritten for loops

• E ffi ciency: May be more e ffi cient than hand written loops

• Correctness: Less code to write means fewer potential errors

• Maintainability: Code is often clearer and more straightforward.

• Scientific software tends to shy away from algorithms because of e ffi ciency concerns/lack of knowledge, but you should prefer clear code until it’s proven there’s a performance bottleneck.

• There are cases where algorithms will be less e ffi cient, and that’s why you should be testing and profiling your code!

Exercise 5: Algorithms

• Write a function that

1.Creates a vector with 10 elements

2.Fills the vector with random integers between 0 and 100

3.Outputs the contents of the vector to screen

4.Outputs the max and min integer in the vector

5.Sorts the vector and outputs to screen again

Hint: Consult the SGI documentation to find algorithms!

Day 4 Homework

• Use your FileReader, ParticleInfo and Particle classes to produce an ordered list of invariant masses formed between mu+ and mu- particles in observed_particles.dat file

• You should not need to use arrays!

• The solution should only use STL containers, and you shouldn’t need to hard code particle masses (Hint: Use your ParticleInfo class!)

• Use STL algorithms as appropriate to simplify the calculation

• Don’t forget to link the application to the FileReader library is you are building this separately!

Download