Object-Oriented Programming in C++ More examples of Association
Relationships between classes Consider “A flat is a type of residential property” “A flat has a kitchen” What can we say about these two statements? – Identify the nouns Flat Residential Property Kitchen
Concept of Containment “A flat is a type of residential property” – “is a” implies inheritance “A flat has a kitchen” – “has a” implies that one class contains another Called Containment – Flat can contain an instance of kitchen – Or can contain a pointer to a kitchen
Containment Flat -theKitchen: Kitchen Kitchen -numSockets: int Flat -pKitchen: Kitchen * Kitchen -numSockets: int
Class to be contained class Kitchen { private: int numSockets; public: Kitchen() {numSockets=0;} void setNumSockets(int); int getNumSockets(); }; Kitchen.cpp #include "Kitchen.h" void Kitchen::setNumSockets(int nSockets) { numSockets = nSockets; } int Kitchen::getNumSockets() { return numSockets; } Kitchen.h
Class containing an instance Flat.h #include "Kitchen.h" class Flat { private: Kitchen theKitchen; public: Flat() {} int getKitchenSockets() { return theKitchen.getNumSockets();} };
Containing an Instance app.cpp #include #include "Flat.h" using namespace std; int main(void) { Flat f; cout << “Flat has " << f.getKitchenSockets() << " sockets." << endl; return 0; } Output: My flat has 0 sockets.
Construction and Destruction A Flat contains a Kitchen instance the Kitchen constructor is called when the Flat is constructed when the Flat goes out of scope, its destructor is called automatically this will cause the Kitchen to go out of scope – so its destructor will be called
Constructing the Kitchen notice that we will always construct a kitchen with 0 sockets the default constructor for Kitchen is called when the flat is constructed it would be better to specify the number of sockets in the constructor of both Kitchen and Flat
Constructing a kitchen with variable sockets add another Kitchen constructor Kitchen(int nSocks) : numSockets(nSocks) {} and another Flat constructor Flat(int nKitchenSockets) : theKitchen(nKitchenSockets) {} in main, construct a luxury kitchen Flat f(3); cout << "Flat has " << f.getKitchenSockets() << " sockets." << endl; output: Flat has 3 sockets.
Containing a Pointer Flat.h #include "Kitchen.h" class Flat { private: Kitchen *pKitchen; public: Flat(int nKitchenSockets):pKitchen(new Kitchen(nKitchenSockets)) {} ~Flat() {delete pKitchen;} int getKitchenSockets() { return pKitchen->getNumSockets(); } };
Containing a pointer no change in app.cpp int main(void) { Flat f(3); cout << "Flat has " << f.getKitchenSockets() << " sockets." << endl; return 0; } Output: Flat has 3 sockets.
Construction and Destruction here we construct a new Kitchen object when we construct a new Flat we need to provide a destructor to delete the Kitchen when the Flat is deleted here the containment relationship is clear – the Kitchen cannot exist if the Flat is destroyed
Alternative we could pass the Flat constructor a pointer to a Kitchen object that has been constructed previously Flat(Kitchen *k) : pKitchen(k) {} in main: Kitchen * k = new Kitchen(2); Flat f(k); we need to think about the destructor – should it delete the Kitchen object? – or will there be other objects outside the flat that still refer to it? not likely in this case but what if the case where a Person has an Address? – a Module has a Teacher?
Difference between Instance and Pointer A pointer is essential if we want to change which object we point to during the lifetime of the container object – ie change the Award taken by a Student Using a pointer, the contained object – can be passed as an argument to the Constructor of the container, – or set via a member method of the container class Using polymorphism, the contained object could be a subtype of the member type – the Student could be on a BSc or MSc award
Difference between Instance and Pointer Using a pointer, the container class can contain a member attribute that is a pointer to an instance of the same class as the container object Person class could have a "mother" attribute this could be a pointer to another Person object not possible with an instance member can't define a member of class Person before the class Person has been defined
Person containing a Person class Person { private: Person mother; string name; public: Person(string n):name(n) { } Person getMother() {return mother;}; void setMother(Person m) { mother = m; } }; not allowed: error C2460: 'Person::mother' : uses 'Person', which is being defined
Person containing a Person pointer class Person { private: Person * mother; string name; public: Person(string n):name(n) { } Person * getMother() {return mother;}; void setMother(Person * m) { mother = m; } }; OK – mother is a pointer, which is the same size no matter what is being pointed to
Circular dependencies What if class A contains a member of type class B and class B contains a member of type class A? Circular Dependencies are very difficult in C++ Class B needs to be defined before it is used in Class A, but Class B needs Class A to be defined first Compiler will complain solution is to use a forward class declaration – similar to using a method prototype
Forward declaration in A.h: class B; // forward declaration class A { public: B* b; }; in B.h: class A; // forward declaration class B { public: A* a; };
Virtual Destructors in an inheritance hierarchy it is important to declare the base class destructor virtual if any of the derived classes contain pointers then the correct destructor will be called when a polymorphic container object is deleted – which should delete the objects pointed to destructors run in the opposite order to constructors – bottom up – derived class to base class
One-to-many association a Person could have many Accounts – might have different subtypes a Vehicle could have one or more Engines – Airplane one-to-many association is implemented using an array or container class – we will discuss C++ containers next lecture good for polymorphism – declare the array or container to hold the base type – fill it with objects of different subtypes
A Person with many vehicles class Person { private: vector theVehicles; public: Person(){} virtual ~Person() {} void addVehicle(Vehicle * v) { theVehicles.push_back(v); } void moveVehicles() { for (int i=0; i < theVehicles.size(); i++) { theVehicles[i]->move(); } };
Using Person #include "Vehicle.h" #include "Person.h" #include using namespace std; int main(){ Person p; p.addVehicle(new Vehicle("Transporter 54")); p.addVehicle(new Airplane("Tornado 2431", 14)); p.addVehicle(new Car("Ford Anglia 22")); p.moveVehicles(); }
Comparison to Java in Java, all member variables are contained by reference (pointer) unless they are primitive types – int, boolean, char, double we construct them with the keyword new – either in the constructor body – or elsewhere, and assign them to the attributes reference counting is taken care of the garbage collector which runs periodically and deletes all objects with no references
Summary In this lecture we have: discussed association compared – containment by instance – containment by pointer looked at destructors in more detail considered circular dependencies looked at one-to-many association