Dynamic Polymorphism in C++ Tom Latham

advertisement
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
Download