Dynamic Polymorphism in C++ Tom Latham Recapping Day 4: Templates • Templates in C++: Static Polymorphism. • Identified concepts in some of our functions as being generic and reimplemented them as function templates. • C++ Standard Template Library: Generic {associative, sequence} containers, algorithms and iterators. • Used templates and the STL to implement the ParticleInfo database object. Today: From Static to Dynamic Polymorphism • Polymorphism • The ability of one generic type to behave in many concrete ways. • We saw last week how templates enable this, but • The polymorphism is locked at compile time, hence the term static • Today we ll see how we can achieve runtime, or dynamic, polymorphism • This is achieved through type-sharing • In C++, the mechanism for this is public inheritance. Starter Files for Today s Exercises • On the webpage for today, you ll find a link to Ben’s github repository tag v4.1.0 for pp6calculator including files which you ll need for today s exercises • We ve applied a few changes to make today s code easier to write: • Add Name and Charge data members to Particle. • Determine Particle name(PDG code), mass and charge using ParticleInfo and PDG code(name) supplied to Particle constructor. • Additional member/free functions and streaming operators for Particle and a new function for FileReader that allows you to interpret each line in an appropriately formatted file as, e.g., a Particle object: Particle bob = reader.getLine<Particle>(); • Implement ParticleInfo as a Singleton for easier access. Aside: The Singleton Design Pattern The Singleton is a design pattern for a class which has one, and only one, instance, has a global point of access, and is guaranteed to be initialised before first use. The basic implementation involves a private constructor/destructor, plus private and unimplemented copy constructor/ assignment operator. There is also a private static data member which is a pointer to the single instance, plus a public static method which returns a pointer or reference to this instance. Notes See the Gang of Four book for more details: Be warned that Singletons are overused and can be a sign of bad design. Types • We ve used the word type in relation to the objects in our code several times: • Built-in types: int, double, char, … • User-defined types: ThreeVector, FourVector, … • Families of types: vector<int>, vector<double>, vector<char>, … Sharing Type • Software design often throws up cases where a set of types all exhibit an Is a Kind Of relationship to some, more abstract, concept. • e.g. Car, Bus, Lorry are all kinds of Vehicle • That is, they all share a type, which is Vehicle • What this means in C++ terms is that they all provide the same interface, i.e. the same set of public member functions, e.g. • int changeGear(); • bool startEngine(); Sharing Type in a Strongly Typed Language • We might want to store instances of, say, Car, Motorbike, Scooter in a container vector<??> caughtSpeeding; • We might design a class that has a data member that may change between, say, Car, Motorbike, Scooter as time changes: class Employee { private: ?? personalVehicle; }; • Unfortunately, neither is possible directly as C++ is a “strongly typed” language, i.e. we have to supply, at compile-time, the type “??”. • We need a mechanism to express the type sharing relationship in the code. Public Inheritance • Type sharing in C++ is achieved through public inheritance. • You create a new class that contains only the signatures of the member functions, i.e. the interface, that defines the common, abstract type. • This class generally contains no data members or code! • Classes that want to share this type publicly inherit from this base class. • What they inherit is the obligation to provide the public member functions specified by the base class. Writing Purely Abstract Base Classes A pABC specifies the interface a type must provide and implement, but no data and no implementation for this interface, as shown below. We see the new C++ keyword virtual prepended to each method signature. This tells the compiler to defer the decision on which actual code to call until runtime. Each method has =0 appended to inform the compiler that no actual implementation is provided for the method - it is pure virtual . Notes It’s important to note that the destructor must be specified and must be virtual. This is so that classes inheriting the pABC are destructed correctly. Exercise 1: A basic pABC for a selection cut interface • Write a pABC called Cut • It should have two pure virtual member functions: • Both of whose name is select and neither of which change the calling instance • One takes a Particle object as its argument and returns a decision on whether or not that Particle is selected, expressed as a bool • The other takes as input a std::vector of Particle objects and returns another that contains only those that are selected • It will also need a virtual destructor Recap on Public Inheritance • It is the mechanism by which we inform the compiler that our concrete classes - those from which we create instances - also hold the type of the abstract base class • The terms derived class and base class are often used to describe this inheritance relationship • Public inheritance means that all the public methods in the base class remain public in the derived class (hence they share an interface). • C++ also allows private and protected inheritance, which result in sharing of implementation but not shared type • Composition/Aggregation are better design patterns in these cases. Writing Derived Classes To derive a class, say Car, from our Vehicle base class, we create class Car and append : public Vehicle, as shown below, to make Car publicly inherit from Vehicle. The Car interface must include all the pure virtual method signatures from Vehicle, plus any specific to Car (like the constructor) and data members relevant to Car. The implementation of the virtual methods for Car follows just as for any other class. Notes Like any other type, we have to ensure the declaration of the base class is present before we can inherit from it. As the derived class will implement the pure virtual methods, these are still declared virtual in Car, but we remove the =0 marker. Exercise 2: Deriving a Mass Cut class • Write a concrete class to make a selection on the mass of a particle, which publicly inherits from the Cut pABC • Name the class MassCut • Implement the constructor(s), destructor • Implement the select methods to apply the selection to a single Particle object and a list of them • Some design hints are on the next few slides... • Add an operation to pp6calculator to create an instance of MassCut and use it to select particles with mass greater than 0.4 GeV/c2, printing out those that pass • You should read the Particle objects from the particles.dat file supplied on today’s webpage Exercise 2: Hints • Think about the constructor(s) of MassCut • You will need to specify: • The type of cut (less-than, greater-than, a range, etc.) • The value(s) to be used in the cut • Should perform simple checks, e.g. that the values are the right way round in a range-type cut • Data members: the cut type and value(s) Exercise 3: Implementing other concrete classes • Repeat what you’ve done for MassCut for two further concrete classes that apply cuts based on the particle energy and momentum (magnitude) • Add two new operations to pp6calculator that create an instance of these new classes and apply cuts to the input list of particles: • Energy in the range 2.0 to 2.5 GeV • Momentum less than 2.25 GeV/c Finally, Dynamic Polymorphism • We ve now got implementations of three concrete types, MassCut, EnergyCut, and MomentumCut, which also have the shared type Cut • Now we can take advantage of the combination of • Type Sharing (via public inheritance) • Late binding/dynamic dispatch (via virtual methods) • So we can now address a set of different concrete instances of derived classes through a pointer or reference to the base class Using Dynamic Polymorphism If you remember back to our example, we wanted to hold different concrete types in a (type homogeneous) vector. As we can see below, we create a vector holding pointers to our abstract type Cut. Via dynamic polymorphism, we can fill this vector with the different concrete classes of Cut because they have the Is A Kind Of relationship. When we message the base class pointer, we get different behaviour depending on the concrete type of the instance pointed to. Notes We can only achieve dynamic polymorphism via pointers or references. Exercise 4: Dynamic Dispatch • Add an operation to pp6calculator that creates a container holding pointers to Cut • Provide an interface to read in the required set of cuts from a text file, and add appropriate instances to the container • Provide an interface to loop through the container, applying in turn the selections to the list of particles • Think about how to clean up the collection of Cut objects to avoid memory leaks Garbage Collection • As hinted at in the Exercise 4 guide, if you used new to create the concrete Cut instances, then you also need to use delete to clean them up • This is relatively awkward since you have to iterate through the container, deleting as you go • Also, what happens if you copy the vector and the copy (or the original) then deletes the pointers? • Thankfully, there are ways to handle both cases safely and automatically Smart Pointers Instead of using raw pointers, we can hand them over to an object known as a smart pointer. This object hold the pointer, provides transparent access to it (so working with the smart pointer is essentially the same as working with the raw) and implements the policies controlling how and when the object pointed to is deleted. The Boost libraries provide a shared_ptr object. This tracks how many copies of a given pointer are in use, deleting the pointer once the last copy of the shared_ptr goes out of scope Notes C++ does provide the auto_ptr object, but this does not behave as you might expect, and should never be used in collections! See the Boost Docs for more on shared_ptr: Smart Pointers Instead of using raw pointers, we can hand them over to an object known as a smart pointer. This object hold the pointer, provides transparent access to it (so working with the smart pointer is essentially the same as working with the raw) and implements the policies controlling how and when the object pointed to is deleted. The Boost libraries provide a shared_ptr object. This tracks how many copies of a given pointer are in use, deleting the pointer once the last copy of the shared_ptr goes out of scope Notes C++ 11 provides a set of smart pointers, including shared_ptr and helper functions, such as make_shared. To convert this code to use C++11, simply replace the include with <memory> and replace boost with std in the scope resolution. For more details, see for example this page. Exercise 5: Using boost::shared_ptr • Modify the collection of pointers to Cut you implemented to use boost::shared_ptrs instead. • Hint: Add the following two lines to your top level CMakeLists.txt to find and use Boost: • find_package(Boost REQUIRED) • include_directories(${Boost_INCLUDE_DIRS}) • Add cout statements to the destructors of the Cut base and derived classes. Do the objects actually get deleted when you expect them to be, e.g. when vector clear is called or when the vector goes out of scope? Abstract Base Classes • You have probably noticed that you had to write lots of identical code when creating the 3 concrete classes – this is inefficient! • We could move some of this code up into the pABC but then it wouldn’t be purely abstract any more and its job is to simply define a type • So instead we add a new layer in the inheritance structure, an Abstract Base Class (or ABC), which inherits from the pABC and from which the concrete classes inherit (instead of from the pABC) • So we have the pABC that defines the type and the ABC that allows some sharing of implementation Protected access • This is the third category of access specifier (public, protected, private) • Depending on how exactly you implement things (as usual, there are many different possibilities!) you may or may not come across a need for protected access in the final exercise • It is useful in any scenario where you have a utility function in the base class that needs to be called by the derived classes • You don’t want it to be public but making it private hides it from the derived classes • Making it protected means that it can be accessed by the class itself and any classes that derived from it Exercise 6: Adding the ABC layer • Create an class called NumericCut that inherits from Cut • Move into it all the shared code from the concrete classes • You should find that you still have at least one pure virtual method • Modify the concrete classes to inherit from NumericCut instead of Cut and to make use of the shared functionality it provides