Introduction to C/C++ Day 3 Mark Slater Adapted from original material by

advertisement
Introduction to C/C++ Day 3
Mark Slater
Adapted from original
material by
Andrew Bennieston
Overview
1. Object Orientation
2. Classes
3. Public and Private Access
4. Operator Overloading
5. Designing a Particle Class
1. Object Orientation
Object Oriented Programming (1)
●
●
We are now in a position to fully introduce the concept of
object-oriented (OO) programming.
This is a different paradigm to the one we have been using so
far - until now we have thought of a program as starting at the
top and working down, calling functions along the way. This is
procedural programming.
x
y
z
t
v
x
y
z
v
boost_z()
main()
x
y
z
L
t
interval()
Using a FourVector struct has
tidied up the function calls
somewhat, but it is still basically
the same program...
Object Oriented Programming (2)
●
●
●
OO programming changes the focus so that we are thinking about the creation and
manipulation of complex objects which have well-defined properties.
The information associated with the object (here, an energy and three momenta) is
encapsulated within the object, and hidden from the calling program, except through a
well-defined interface.
If you want to know the interval of a FourVector object, you ask the object and it tells
you... you don't have to care about how it arrived at the number!
Note that this was what we were doing with Opaque Pointers!
FourVector
v
x
boost_z()
main()
y
z
interval()
L
t
The main program and the
FourVector code are now
disconnected.
Anything to do with
properties of the object is
handled by the object
itself!
Benefits of OO
There are some significant advantages to using good OO design:
➔
➔
➔
➔
➔
Simplified code – easy to transfer a single object rather than many objects
Divides code into small, logically related pieces
Each piece can be dealt with in isolation
Improves sharing and reuse
Complex behaviour can be created by combining objects in different ways
The best way to learn good OO design is to practice, but beware –
while good OO design will improve your code, bad design will
render it unusable!
2. Classes
Introduction to Classes
●
●
Classes are how we create the user-defined objects we need in
C++
Classes and structs are almost identical; just use the class
keyword instead of struct, and add the public keyword (more
on this later...)
struct FourVector {
double t;
double x;
double y;
double z;
};
class FourVector {
public:
double t;
double x;
double y;
double z;
};
Member Functions
●
Classes can contain member functions as well as member
variables. These have access to the data associated with an
instance of the class:
class FourVector {
public:
double interval() const;
double boost_z(const double velocity);
double
double
double
double
};
t;
x;
y;
z;
These are now member
functions which return the
interval, and perform the
boost. They work on the
variables attached to an
object instance.
Note that these are function
declarations; you still need
to define them!
Defining Member Functions
●
●
The code on the previous slide declares a class called
FourVector which has two member functions and four member
variables. It doesn't define the code associated with the
functions.
To do this, we must provide a function definition similar to
before. The difference is that it must now be defined in the
scope of the class, using the scope resolution operator ::
double FourVector::interval() const
{
// Assume natural units
double s = t*t – x*x – y*y – z*z;
return s;
}
Aside – Using const
●
●
In the previous declarations/definitions we have declared
functions as const
You can do this for:
➔
➔
➔
●
●
Variables (e.g. const int) – the contents of the variable can't be changed
Function arguments – the function can't alter the object
Member functions
In this last case, what you're saying is that you guarantee that
this member function does not change the state of the object –
you can still access the variables (read their values) but
attempting to change them results in a compiler error.
Note that this becomes useful when you start passing objects
of user-defined type around as function arguments. If an
object is passed through a reference-to-const, you will only be
able to call const member functions of that object!
A Note on Naming Conventions
●
●
●
●
Now we have local variables, member variables, classes, functions, and
member functions, it can be important to think about naming
conventions...
It is hard to distinguish a member variable from a local variable inside a
member function. To help with this, it can be useful to implement (and
stick to!) a naming convention.
Examples include:
➔
m_ or f_ prefix for member variables (“member” or “field”)
➔
Trailing underscore for member variables
➔
Function names all_lowercase, member functions in camelCase
➔
Classes with InitialLettersCapitalised
➔
Prefix class names with T or C (“type” or “class”; the ROOT convention...)
Which scheme you use is entirely a matter of taste. The important thing
is that you choose one and stick to it. Chances are your experiment will
already have an established convention, so it is probably best to use that!
Exercise 3
●
●
●
●
Modify your struct into a class with member functions boost_z() and
interval().
You should be able to use a similar file structure as with the opaque
pointers exercise earlier. You will need to change the
declaration/definitions though!
You now won't need to pass any arguments to the interval()
function (not even a pointer) since all the things you need are
member variables associated with an object of that type. You'll still
need to pass a velocity to the boost_z() function!
Hints:
➔
➔
➔
Member functions are accessed in the same way as member variables, i.e.
object.myFunction() or ptr_to_object->myFunction()
You can't initialise objects of class type using the array-style initialiser we used
for structs; for now you'll have to set each field in turn.
You must include the public keyword!
Constructors (1)
●
●
●
You'll notice that you have to assign values to the member
variables individually, at present. To properly initialise objects
of class type, there are special member functions known as
constructors.
These have the same name as the class itself, and are called
whenever an object of that type is constructed.
They are mostly used to initialise variables, but they can
contain any code you like, so they can perform complex tasks
like opening a file, establishing a database connection,
allocating memory or computing some quantity.
Constructors (2)
The constructor can take any parameters you like and is defined
just like any other member function. You can also have multiple
constructors for a class, thanks to function overloading.
class FourVector {
public:
// ctors
FourVector() { t = 0; x = 0; y = 0; z = 0; }
FourVector(const double t_, const double x_,
const double y_, const double z_);
FourVector(const FourVector& other);
// member functions
double interval();
double boost_z();
};
// member variables
double t;
double x;
double y;
double z;
First ctor takes no arguments
and sets all member vars to 0.
Note that you can put simple
function definitions in the
class declaration!
Second ctor takes four
arguments. Note that we
have declared it but not
defined it – there is no
function body!
Third ctor is a copy
constructor – it takes a
reference to another
object of the same type,
allowing us to make
copies...
Constructors and Initialiser Lists
●
Like any other member function, you can define constructors outside the class
declaration. Here are the definitions for the second and third constructors,
which we didn't provide in the declaration:
FourVector::FourVector(const double t_, const double x_,
const double y_, const double z_)
: t(t_), x(x_), y(y_), z(z_)
{}
Initialiser lists
FourVector::FourVector(const FourVector& other)
: t(other.t), x(other.x), y(other.y), z(other.z)
{}
●
●
Note the use of initialiser lists. When a constructor is called, the member
variables are all default-initialised before the body of the constructor runs.
Assigning values in the constructor body results in a default-initialisation
followed by assignment, while assigning in an initialiser list will initialise the
variables with the values you specify, which is more efficient.
In fact, const member variables and references must be set with initialiser lists,
since once they have been initialised they cannot be modified – even in the
body of the constructor!
Destructors
●
●
Destructors are the opposite of constructors; member
functions which are called when an object is deleted (i.e. when
stack objects go out of scope, or delete is called on heap
objects).
In analogy to constructors, they use the class name, but have a
tilde ~ before the name:
class SaferDynamicArray {
public:
SaferDynamicArray(int size)
{ array = new int[size]; }
In the ctor we dynamically
allocate an array...
~SaferDynamicArray() { delete[] array; }
};
int* array;
In the dtor we delete it to
prevent a memory leak!
RAII
●
●
●
●
RAII stands for 'Resource Acquisition Is Initialisation' and is
the concept behind the example on the previous slide.
It deals with resource deallocation such as deleting memory
allocated through new, closing files, ending database
connections, etc.
The idea is to use a constructor to initialise an object with an
acquired resource (memory, file handle, database connection,
etc.) and a destructor to free the resource once the object goes
out of scope.
Because the resource is tied to the object, and the destructor is
always called when an object's lifetime comes to an end, the
resource will always be deallocated.
Exercise 4
●
Add constructors to your FourVector class.
●
Points to think about:
➔
➔
The different constructors you could provide (default, ctors with
arguments, copy ctor...). Try to add them as appropriate.
Does your FourVector need a destructor? Maybe try adding one anyway
and use std::cout to print messages from the body of the ctor & dtor to see
when they get called...
3. Public and Private Access
Public and Private Access (1)
●
●
●
Switching from structs to classes introduced access control.
You had to use the public keyword to give your class members
the same visibility as the struct members used to have.
If a member function or member variable is declared public,
any code can call the function or access the variable. If it is
private, only other member functions of the same class can use
it.
Test this by attempting to compile your code with the public
keyword changed to private...
Public and Private Access (2)
●
●
●
●
You should have seen a number of compiler errors when you
changed from public to private. One of the main reasons for
using private access is to hide implementation details of your
class from code outside.
For example, if you store a 3-vector, you could store (x,y,z) or
(r,θ,ϕ) internally and still provide member functions to
retrieve (x,y,z) or the length of the vector.
The code on the outside doesn't need to know about the
internal representation, so long as it can ask for the length, or
a value in some coordinate system
This allows us to do what we wanted this morning with opaque
pointers – completely hide the implementation of the object so
external code doesn't depend on it.
Public and Private Access (3)
●
●
●
Another example would be an optimisation of the FourVector
class. We could have the interval pre-computed so that it isn't
calculated each time you call the interval() method.
However, if the user was to change one of the variables
directly, the pre-computed interval would no longer be valid.
Consequently, you should make the member variables private,
and provide public access methods to read and write them.
Public and Private Access (4)
class MyClass {
public:
// ctors & dtors
MyClass() {}
~MyClass() {}
// public member functions
double foo();
double bar(double v);
};
private:
double num;
double a;
void myfoo();
By default, the access
mode is private for classes.
Switch to public for the
interface...
You should make sure your
constructors & destructors
are public – otherwise
nobody will be able to
make an instance of your
class!
You can have private
member functions as well
as private member
variables!
You can switch between
public and private multiple
times by using the
keywords as appropriate –
but it may get confusing!
Exercise 5
●
●
●
Apply the appropriate access rights to the member variables and
member functions of your FourVector class.
Make the variables private and provide additional public
member functions to get and set their values.
Move the code from the public interval() function to a private
one which pre-computes the interval... you should call this
from the ctor and whenever a variable is modified.
4. Operator Overloading
Operator Overloading
●
●
●
●
You now have a FourVector class that
provides some useful member functions and
can be used in a similar way to the built-in
types.
However, unlike the built-in types, you can't
use mathematical operators on FourVectors.
If you want to be able to do something like
the code in the box, you have to tell the
compiler how to perform this operation.
You do this by providing code to implement
the required operations, so that when the
compiler sees an operator it knows what to
do.
This is called operator overloading and
provides a powerful mechanism for making
user-defined types behave identically to
built-in types.
int main()
{
FourVector a;
FourVector b;
FourVector c = a + b;
}
return 0;
Operator Overloading
●
As we've already seen, C++ provides a number of different
operators, working on a number of different operands:
<object1> <operator> <object2> // binary: *, +, /
<object1> <operator> // unary postfix: ++, --, []
<operator> <object1> // unary prefix: ++, --, -
●
●
When you apply an operator to an object of user-defined type, the
compiler calls a function which you can provide the definition of.
These are often (but not always) member functions.
The declaration depends on whether they take one argument or
two, in addition to the return type. Some examples follow:
A = B; // A is return value, B is argument (member function of A) [copy assignment]
C = (A + B); // C is return value, A and B are arguments (free function) [addition]
(More) Operator Overloading
class MyClass {
public:
MyClass() : a(0) {}
MyClass(double val) : a(val) {}
MyClass(const MyClass& other) : a(other.a) {}
~MyClass() {}
Declaring operators for
addition-with-assignment,
and for copy-assignment.
These affect the object
itself so are declared as
members.
MyClass& operator+=(const MyClass& rhs)
{ a += rhs.a; return *this; }
MyClass& operator=(const MyClass& rhs)
{ if (&rhs != this) { a = rhs.a; } return *this; }
private:
double a;
};
MyClass operator+(const MyClass& lhs, const MyClass& rhs)
{
MyClass temp(lhs);
temp += rhs;
return temp;
}
The addition operator
requires two parameters
since it is declared outside
of the class.
We could have declared it
inside, but it's better to
have it as a free function.
Note that it is
implemented in terms of
the += member operator!
Streaming Operators
●
You can also write operators to allow you to read and write
objects of your new class using the C++ streams. These are
operator>> (for input) and operator<< (for output) and they
must be free (non-member) operators:
std::ostream& operator<<(std::ostream& stream,
const FourVector& v)
{
stream << "[";
stream << v.getT() << ", " << v.getX() << ", ";
stream << v.getY() << ", " << v.getZ() << "]";
return stream;
}
std::istream& operator>>(std::istream& stream,
FourVector& v)
{
double t, x, y, z;
stream >> t >> x >> y >> z;
v.setT(t); v.setX(x); v.setY(y); v.setZ(z);
return stream;
}
Exercise 6
●
●
Add overloads for the operators +=, -= and = to your FourVector
class. Provide overloads for + and – in terms of the member
operators.
Implement the streaming operators and use them to simplify
the input in your main function.
Remember to check for self-assignment (A = A) in the = operator!
Aside: UML Class Diagrams
UML (Unified Modelling Language) class diagrams provide a graphical
representation of the members of a class and how they relate to any other
classes in your program.
Member name(argument
list) :return type
Attributes (member
variables)
Private members
indicated by –
Public members indicated
by +
Operations
(member functions)
5. Designing a Particle Class
Designing a Particle Class
In the homework exercise you’ll have to implement a Particle
class, but let’s take a moment to think about the design of such a
class…
Summary
We’ve covered several important concepts today:
➔
➔
➔
How to decompose a problem into a set of objects and their interactions
How to represent these using classes, member functions and operators
Which variables and methods to encapsulate within a class, and which to
leave as 'free' functions
You’ll use all of these in the homework exercise…
Homework Exercise
Now we have a design that includes Particle, ThreeVector and
FourVector; for the homework exercise:
➔
➔
➔
➔
Split your code into source and header files; use one header and one
source file for each class, as well as a source file for your main() function
Implement the Particle, FourVector and ThreeVector classes according to
the interface we just designed
Rewrite your solution to the Day 2 homework (sorting by invariant mass)
to use these new classes
Keep the directory structure and Makefiles, and remember to commit to
git regularly!
Download