Object-Oriented Programming in C++ More examples of Association

Preview:

Citation preview

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 <iostream>#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 constructorKitchen(int nSocks) : numSockets(nSocks) {}

• and another Flat constructorFlat(int nKitchenSockets) : theKitchen(nKitchenSockets) {}

• in main, construct a luxury kitchenFlat 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.cppint 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 Personclass 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 pointerclass 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 Band 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 vehiclesclass Person {private:

vector<Vehicle *> 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 <vector>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

SummaryIn 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

Recommended