Copyright Hannu Laine C++-programming Part 8 Hannu Laine
HL1 1. Main component / sub component Composition Aggregation "contains " relation "has" relation: Main component "has" a sub component as a part. 2. Association "owns" "knows" "uses" "serves“ “observes” etc. 3. Generalisation / specialisation Inheritance General case / Special case Super class / Sub class Base class / Derived class Base class / Inherited class "is" relation. An instance of derived class "is" an instance of base class. Relations between classes and objects
HL2 Composition means same as - contains - is part of Example 1. Point can be a sub component of Circle. class Circle { public: Circle(float x0=0.0, float y0=0.0, r0=0.0); Circle(const Point &cp0, r0=0.0); private: float r; Point cp; //sub component }; Constuctor implementation on the next page. Example 2. Date can be used as a data member in class Person: class Person { public: Person(const string& name, int year, int month, int day); Person(const string& name, const Date& bd); private: string name; // sub component 1 Date birthDay; // sub component 2 }; Composition
HL3 An object can have an object as a subcomponent. In this example circles have a point as a subcomponent (representing their center point */ #include //Class definition of Point class Point { public: Point(float x0=0.0, float y0=0.0); void read(); void print(); private: float x; float y; }; //Class definition of Circle class Circle{ public: Circle(float x0=0.0, float y0=0.0, float r0=0.0); Circle(const Point &cp0, float r0=0.0); void read(); void print(); private: float radius; Point centerPoint; }; Example page 1/3
HL4 //Application void main (void) { Point p(11.0, 12.0); Circle c1, c2(1.0, 2.0, 10.0), c3(p, 20.0); c1.print(); c2.print(); c3.print(); } //Member function implementations //Member functions of Point Point::Point(float x0, float y0) { x = x0; y = y0; } void Point :: read() { cout << "\n x-coordinate: "; cin >> x; cout << " y-coordinate: "; cin >> y; } void Point :: print() { cout << "(" << x << "," << y << ")"; } Example page 2/3
HL5 //Member functions of Circle //Constructor 1 version 1 Circle::Circle(float x0, float y0, float r0): centerPoint(x0, y0){ radius = r0; } /* Constructor 1 version 2 (Point constructor is called twice) Circle::Circle(float x0, float y0, float r0) { radius = r0; centerPoint = Point(x0, y0); } */ //Constructor 2 version 1 Circle::Circle(const Point &cp0, float r0):centerPoint(cp0){ radius = r0; } /*Constructor 2 version 2 (Point constructor is called twice) Circle::Circle(const Point &cp0, float r0) { radius = r0; centerPoint = Point(cp0); //Default copy constructor of Point } */ void Circle :: read() { cout << "\nEnter radius : "; cin >> radius; cout << "Enter centerpoint :"; centerPoint.read(); } void Circle :: print() { cout << "\nThe circle data is :"; cout << "\nRadius is : " << radius; cout << "\nCenter point is :"; centerPoint.print(); } Example page 3/3
HL6 Memory allocation is done at the same time (as one block of memory) for the main component and for sub component, because sub component is part of the memory area of the main component. The object is created in the following phases: 1. Memory is allocated for the whole object. 2. The constructor call of sub component class that is defined in the initialisation list of main component is executed. If there is no sub component constructor call in the initialisation list, the default constructor of sub component is called. 3. The function body of main component constructor is called. This rule of order means that it is guaranteed that sub component is already constructed when constructor body on main component is executed. It also guarantees that sub components are always initialised at least with default constructor of sub component class. This is useful as you can understand thinking about the differences between defining a name member in the Person class as char *name or string name!. The implementor of the Person class needs to do many things in the former case but almost nothing in the latter case. Memory allocation and constructors
HL7 When an object containing a sub-component object is de-allocated, the things happen in the following order: 1.The destructor of the main component is called first. 2. The destructor of the sub-component is called next. 3. The memory area is de-allocated as a whole. The default assignment operator of the main component: If we don’t overload the assignment operator in the main component class the default assignment is used. If we have overloaded assignment for the sub-component that is used in the default assignment when sub-component part is copied. The default copy constructor of the main component: If we don’t write the copy constructor for the main component class the default copy constructor is used. If we have written copy constructor for the sub-component it is used in the default copy constructor when sub-component part is constructed. Memory de-allocation and destructors
HL8 It is interesting to see how using the classes instead of “raw data” helps things in programming Example 1. (dynamic string as a local variable) Comparison 1 Raw dataClass void f() { char *s, *name = ”abcde”; len = strlen(name); s = new char[len]; strcpy(s, name); // use s delete s; } void f() { char *name = ”abcde”; string s(name); // use s } The right side is much simpler, because constructor and destructor do the most of the things automatically. Remark. Note that the only difference actually is that the “raw pointer” s on the left side is encapsulated inside the class string on the right side.
HL9 It is interesting to see how using the classes instead of “raw data” helps things in programming Example 2. (dynamic string as a member) Comparison 2 Raw dataClass class Person { public: Person(const char *s=””); Person(const Person& p); ~Person(); // assignment needed private: char *name; int age; }; class Person { public: Person(const char *s=””); private: string name; int age; }; The right side is much simpler, because copy constructor, destructor or assignment operators are not needed in the Person class, if they are properly implemented in the string class. This is the consequence of the things described on the pages 6 and 7. Remark. Note that the only difference actually is that the “raw pointer” name on the left side is encapsulated inside the class string on the right side.
HL10 Association can mean for example : - owns - knows - uses - serves - mother/child - and so on Example 1 (One-way association) Circle owns it’s center point. class Circle { public: Circle(float x0=0, float y0=0, r0=0); Circle(const Point &kp0, r0=0); Circle(const Circle &c); ~Circle(); private: float r; Point *cp; }; How constructors are implemented in this case (see the next page). Association 1 (1/2)
HL11 As an example we now implement one of the constructors and the destructor: Circle:: Circle ( float x0=0, float y0=0, float r0=0) { cp = new Point(x0, y0); r = r0; } Circle::~Circle() { delete cp; } Remark. Remember that because this class now has a dynamic data member, we also need to implement a copy constructor and an assignment operator for this class to make it work in reliable way in all kind of situations. Association 1 (2/2)
HL12 Example 2 (Bi-directional association between objects of the same class). Mother child class Person { public: Person(const char *name0, int age0); Person(const char *name0, int age0, Person &mother); void adopt(Person &child); void print(); private: char name[30]; int age; Person *mother; Person *child; }; How constructors are implemented in this case. See the separate example. Association 2
HL13 /* This example demonstrates association(mother/child- relation). Links (pointers) are used to associate mother and child together. This is two-sided relation: Mother has a child and child has a mother. Mother and child can be connected when child is born (constructed) or later using member function adopt. */ #include class Person { private: char name[20]; int age; Person *child; Person *mother; public: Person(const char *name, int age); Person(const char *name, int age, Person &mother); void print(); void adopt(Person &child); }; void main (void) { Person mother1("Maija", 30); Person child1("Sini", 4); Person mother2("Mary", 40 ); Person child2("Sally", 5, mother2); mother1.print(); child1.print(); mother1.adopt(child1); mother1.print(); child1.print(); mother2.print(); child2.print(); } Example 1/2
HL14 //Member function implementations Person::Person(const char *name0, int age0) { strcpy(name,name0); age = age0; mother = NULL; child = NULL; } Person::Person(const char *name0, int age0, Person &mother0) { strcpy(name,name0); age = age0; child = NULL; mother = &mother0; mother0.child = this; } void Person::print() { cout << "\nPersonal data:"; cout << "Name is " << name; cout << " Age is " << age; if (mother) cout name; if (child) cout name; } void Person::adopt(Person &child0) { child = &child0; child0.mother = this; } Example 2/2
HL15 Example 3 (Bi-directional association between objects of different classes). Forward declaration is needed to make compilation possible. class B;//Forward declaration class A {... B *b;... }; class B {... A *a;... }; Association 3
HL16 class Server; class Owner { private:... Server *server;... public: Owner(); } class Server { private:... Owner *owner;... public: Server(Owner *owner); } Owner::Owner() {... server = new Server(this);//the address //of owner is passed.... } Server::Server(Owner *own) {... owner = own; //the address of owner...// is stored } Association 4