Introduction to the second half of SEP200
Relationships Overview
Compositions
Aggregations
Associations
Casting
Electrical Engineer by trade
+20 years of experience in Software Development
Teaching this course for the sixth time!
Brazilian, Canadian, and French
Father of two little honey badgers tasmanian devils beautiful children
→ Compositions, Aggregations and Associations
→ STL: Containers and Iterators
→ Function Pointers and Lambda expressions
→ Error Handling
→ STL: Algorithms
→ Smart Pointers
→ Threads
→ Lectures will be in person
→ Workshops will also be in person - submission online is possible, but not recommended
→ Blackboard will be used for assignments, for announcements, and for grading purposes
→ Course's website contains general info and slides
→ Four Labs (2.5%): In person labs
→ Five Quizzes (1.0%): Blackboard Tests
→ Two Assignments (15%): Presentations
→ One Final Exam (20%): in class pen-on-paper test
Inheritance is one type of relationship between different classes
We will now cover other types of relationships:
→ Composition
→ Aggregation
→ Association
Compositions: The composer object owns and controls the lifecycle of the component object
Aggregation: The aggregator has an instance of another class
Association: One class accesses or uses another class
Compositions:
Objects with other objects as member variables
Aggregation:
Objects with pointers (arrays) to other objects
Association:
Objects that interact with other objects
A composition is a special type of aggregation
An aggregation is a special type of association
Hence, these relationships can be seen in a diagram as shown in the next slide
These relationships have "levels" or "strenghts"
Composition is the strongest relationship
Aggregation is weaker: the aggregator does not determine the lifecycle of the aggregated
Association is the weakest relationship
It is a "has-a" relationship
One object is responsible for creating and destroying its component objects
Frequently used with member variables
Can also be used for pointers - as long as the parent controls the lifecycle
#include <iostream>
#include <cstring>
const int N = 64;
class Name
{
private:
char fname_[N];
char lname_[N];
public:
Name(const char *, const char *);
~Name();
void display();
};
Name::Name(const char *fname, const char *lname)
{
std::cout << "Calling Constructor for Name!"
<< std::endl;
strncpy(fname_, fname, N);
fname_[N - 1] = '\0';
strncpy(lname_, lname, N);
lname_[N - 1] = '\0';
}
Name::~Name()
{
std::cout << "Calling Destructor for Name!"
<< std::endl;
}
void Name::display()
{
std::cout << "Name: " << fname_ << " "
<< lname_ << std::endl;
}
class Person
{
private:
Name name_;
char dob_[N];
char birth_gender_[N];
public:
Person(const char *, const char *,
const char *, const char *);
~Person();
void display();
};
Person::Person(const char *fname, const char *lname,
const char *dob, const char *birth_gender)
: name_(fname, lname)
{
std::cout << "Calling Constructor for Person!"
<< std::endl;
strncpy(dob_, dob, N);
dob_[N - 1] = '\0';
strncpy(birth_gender_, birth_gender, N);
birth_gender_[N - 1] = '\0';
}
Person::~Person()
{
std::cout << "Calling Destructor for Person!"
<< std::endl;
}
void Person::display()
{
std::cout << "----------------------------"
<< std::endl;
std::cout << "Person Data: " << std::endl;
name_.display();
std::cout << "Birth gender: " << birth_gender_
<< std::endl;
std::cout << "Date of birth: " << dob_ << std::endl;
std::cout << "----------------------------"
<< std::endl;
}
int main()
{
Person someone("John", "Doe", "01/01/1985", "male");
someone.display();
return 0;
}
Weaker version of composition
Still carries a sense of ownership
The aggregator is not responsible for creating and destroying its component objects
Frequently used with pointers and arrays of pointers
// code showcasing aggregation
#include <iostream>
#include <cstring>
const int N = 64;
class Assignment
{
private:
char name_[N];
float marks_;
public:
Assignment(const char *, float);
~Assignment();
void display();
};
Assignment::Assignment(const char *name, float marks)
{
std::cout << "Constructor for Assignment." << std::endl;
strncpy(name_, name, N);
name_[N - 1] = '\0';
marks_ = marks;
}
Assignment::~Assignment()
{
std::cout << "Destructor for Assignment." << std::endl;
std::cout << "Deleting: " << name_ << std::endl;
}
void Assignment::display(){
std::cout << "Assignment: " << name_ << std::endl;
std::cout << "marks: " << marks_ << std::endl;
}
class Course
{
private:
char code_[N];
char name_[N];
Assignment *assignments_[16] = {};
int num_assignments_ = 0;
public:
Course(const char *, const char *);
~Course();
void add_assignment(Assignment *);
void display_assignments();
};
Course::Course(const char * code, const char * name)
{
std::cout << "Course has been created." << std::endl;
strncpy(code_, code, N);
code_[N - 1] = '\0';
strncpy(name_, name, N);
name_[N - 1] = '\0';
}
Course::~Course()
{
std::cout << "Course has been deleted." << std::endl;
}
void Course::add_assignment(Assignment *assignment)
{
assignments_[num_assignments_] = assignment;
num_assignments_++;
}
void Course::display_assignments()
{
std::cout << "Assignments list: " << std::endl;
std::cout << "----------------------------"
<< std::endl;
for (int i = 0; i < num_assignments_; i++)
{
assignments_[i]->display();
}
std::cout << "----------------------------"
<< std::endl;
std::cout << "End of Assignments" << std::endl;
}
int main()
{
Course course("SEP200", "Object-Oriented Programming");
Assignment workshop01("Workshop01", 3.0),
workshop02("Workshop02", 3.0),
quiz01("quiz01", 1.5),
quiz02("quiz02", 1.5);
course.add_assignment(&workshop01);
course.add_assignment(&workshop02);
course.add_assignment(&quiz01);
course.add_assignment(&quiz02);
course.display_assignments();
return 0;
}
Weak relationship between otherwise unrelated objects
One class uses elements of another class
It does not involve ownership of any kind
#include <iostream>
#include <cstring>
const int N = 64;
class Course;
class Room
{
private:
char name_[N];
public:
Room(const char *);
~Room();
friend class Course;
};
class Course
{
private:
char code_[N];
char name_[N];
char room_name_[N] = {};
public:
Course(const char *, const char *);
~Course();
void book_room(Room *);
void display_info();
};
Room::Room(const char *name)
{
strncpy(name_, name, N);
}
Room::~Room()
{
std::cout << "Destructor for Room." << std::endl;
}
Course::Course(const char *code, const char *name)
{
strncpy(code_, code, N);
code_[N - 1] = '\0';
strncpy(name_, name, N);
name_[N - 1] = '\0';
}
Course::~Course()
{
std::cout << "Course has been deleted." << std::endl;
}
void Course::book_room(Room *room)
{
strncpy(room_name_, room->name_, N);
room_name_[N - 1] = '\0';
}
void Course::display_info()
{
std::cout << "Course code: " << code_ << std::endl;
std::cout << "Course name: " << name_ << std::endl;
std::cout << "Room: " << room_name_ << std::endl;
}
int main()
{
Course sep200("SEP200", "Object-Oriented Programming");
Course btl200("BTL200", "Mathematics for Developers");
Room c3032("C3032"), e2042("E2042");
sep200.book_room(&c3032);
sep200.display_info();
btl200.book_room(&e2042);
btl200.display_info();
return 0;
}
You can still have pointers to objects of other classes in associations
As long as there isn't a sense of ownership
In the code that follows, it is clear that the classes Course and Room do not own one another
#include <iostream>
#include <cstring>
const int N = 64;
class Course;
class Room
{
private:
char name_[N];
Course *course_;
public:
Room(const char *);
~Room();
void display_info();
friend class Course;
};
class Course
{
private:
char code_[N];
char name_[N];
Room *room_;
public:
Course(const char *, const char *);
~Course();
bool book_room(Room *);
void display_info();
friend class Room;
};
Room::Room(const char *name)
{
strncpy(name_, name, N);
this->course_ = nullptr;
}
Room::~Room()
{
std::cout << "Destructor for Room." << std::endl;
}
void Room::display_info()
{
std::cout << "Room name: " << name_ << std::endl;
if (course_ == nullptr)
{
std::cout << "Room currently empty" << std::endl;
}
else
{
std::cout << "Course code: " << course_->code_
<< std::endl;
std::cout << "Course name: " << course_->name_
<< std::endl;
}
std::cout << "----------------------------"
<< std::endl;
}
Course::Course(const char *code, const char *name)
{
strncpy(code_, code, N);
code_[N - 1] = '\0';
strncpy(name_, name, N);
name_[N - 1] = '\0';
room_ = nullptr;
}
Course::~Course()
{
std::cout << "Course has been deleted." << std::endl;
}
bool Course::book_room(Room *room)
{
if (room->course_ == nullptr)
{
room->course_ = this;
room_ = room;
return true;
}
else
{
std::cout << "Room already booked" << std::endl;
return false;
}
}
void Course::display_info()
{
std::cout << "Course code: " << code_ << std::endl;
std::cout << "Course name: " << name_ << std::endl;
if (room_ == nullptr)
{
std::cout << "Room: " << "TBD" << std::endl;
}
else
{
std::cout << "Room: " << room_->name_ << std::endl;
}
std::cout << "----------------------------"
<< std::endl;
}
int main()
{
Course sep200("SEP200", "Object-Oriented Programming");
Course btl200("BTL200", "Mathematics for Developers");
Room c3032("C3032"), e2042("E2042");
sep200.book_room(&c3032);
btl200.book_room(&c3032);
sep200.display_info();
btl200.display_info();
btl200.book_room(&e2042);
btl200.display_info();
c3032.display_info();
e2042.display_info();
return 0;
}
The compiler records the types of all variables
There are situations in which a variable needs to be recasted into a different type:
→ Upcasting a derived object as a base object
→ Downcasting a base object into a derived object
→ Numerical conversions (e.g., int to double)
→ API interfacing
The following methods are provided to recast types:
static_cast: performs type conversion at compile-time
dynamic_cast: performs type conversion at run-time
const_cast: modifies const or volatile qualifier
reinterpret_cast: type conversion without checks
#include <iostream>
#include <typeinfo>
int main()
{
int num = 10;
std::cout << "10/3 = " << num/3 << std::endl;
double numDouble = static_cast<double>(num);
std::cout << "10/3 = " << numDouble/3 << std::endl;
std::cout << typeid(num).name() << std::endl;
std::cout << typeid(numDouble).name() << std::endl;
return 0;
}
#include <iostream>
class Animal {
public:
virtual void makeSound() const {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "Bark bark!" << std::endl;
}
void fetch() {
std::cout << "Fetching ball!" << std::endl;
}
};
int main() {
Dog fido;
//upcasting
Animal *animal = (&fido);
animal->makeSound();
//animal->fetch(); //error
return 0;
}
#include <iostream>
class Animal {
public:
virtual void makeSound() const {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "Bark bark!" << std::endl;
}
void fetch() {
std::cout << "Fetching ball!" << std::endl;
}
};
int main() {
Dog fido;
//upcasting
Animal *animal = dynamic_cast<Animal *>(&fido);
//downcasting
Dog *fido_recovered = dynamic_cast<Dog *>(animal);
fido_recovered->makeSound();
fido_recovered->fetch();
return 0;
}