FAC1003 Tutorial 14 — Solution Strategies
Core theme: This tutorial covers the four pillars of OOP (Encapsulation, Abstraction, Inheritance, Polymorphism) and their implementation in C++. The programming questions build from simple class design up to polymorphic class hierarchies.
Concepts (Questions 1-8)
1) OOP vs Procedural Programming
OOP (Object-Oriented Programming) is a paradigm that organizes code around objects (bundles of data + behavior), while procedural programming organizes code around functions that operate on separate data.
| Aspect | OOP | Procedural |
|---|---|---|
| Organization | Objects (data + methods) | Functions |
| Data access | Encapsulated (private) | Global/shared |
| Reuse | Inheritance, composition | Function libraries |
| Real-world mapping | Direct (objects model entities) | Indirect |
Why it works: OOP models real-world entities as objects with attributes (data) and behaviors (methods). This makes large programs more maintainable because changes to one object's internal implementation don't ripple through the codebase — as long as the interface stays the same.
2) Encapsulation
Encapsulation bundles data and methods that operate on that data within a single unit (class), hiding internal state and requiring all interaction to go through public member functions.
C++ example:
class BankAccount {
private:
double balance; // Hidden from outside
public:
BankAccount(double initial) : balance(initial) {}
void deposit(double amount) {
if (amount > 0) balance += amount;
}
double getBalance() const {
return balance; // Read-only access
}
};
Why it works: The private: label prevents direct modification of balance. You can't accidentally do account.balance = -1000 — you must use deposit(), which includes validation. This protects invariants (rules that must always be true, like "balance must never go negative due to deposits").
3) Abstraction
Abstraction means exposing only essential features while hiding implementation details. The user of a class interacts with a simplified interface without needing to understand how it works internally.
Why it's useful:
- Reduces complexity: A car driver uses the steering wheel, not the engine cylinders
- Enables change: You can rewrite the internals without affecting users
- Hides distractions: Users see only what they need
Real-world analogy: A TV remote — buttons for power, volume, channel. You don't need to know about the circuit board inside.
4) Inheritance vs Polymorphism
| Concept | What it is | C++ mechanism |
|---|---|---|
| Inheritance | A class (derived) gets properties from another class (base) | class Dog : public Animal {} |
| Polymorphism | The same interface behaves differently based on the actual object type | Virtual functions, function overloading |
Why the distinction matters:
- Inheritance is about code reuse ("is-a" relationship)
- Polymorphism is about runtime flexibility (treating different types uniformly through a base pointer)
Example: Shape* s = new Circle(); s->area(); — the correct Circle::area() runs even though the pointer type is Shape*. This is runtime polymorphism via virtual functions.
5) Four Pillars of OOP
| Pillar | Description | Example |
|---|---|---|
| Encapsulation | Bundle data + methods, hide internals | private: members with public getters/setters |
| Abstraction | Show only essential features | Pure virtual functions define an interface |
| Inheritance | Derive new classes from existing ones | class Dog : public Animal {} |
| Polymorphism | Same interface, different implementations | Virtual functions, function/operator overloading |
Why these four: They work together — Encapsulation protects data, Abstraction simplifies interfaces, Inheritance enables code reuse, and Polymorphism enables flexible, extensible designs. Missing any one makes the other three less effective.
6) Class vs Object
| Class | Object | |
|---|---|---|
| Definition | Blueprint / template | Concrete instance |
| Memory | No memory allocated when defined | Memory allocated when created |
| Creation | Written once in code | Created at runtime |
| Analogy | A cookie cutter | A cookie |
| Analogy 2 | A house blueprint | The actual house built from it |
C++:
class Car { // Class = blueprint
string model;
int year;
};
Car myCar; // Object = actual car instance
7) Constructor and Destructor
| Constructor | Destructor | |
|---|---|---|
| When called | When object is created | When object goes out of scope / is deleted |
| Purpose | Initialize data members | Clean up resources (heap memory, file handles) |
| Name | Same as class name | Same as class name with ~ prefix |
| Return type | None (not even void) | None |
| Can have parameters? | Yes | No |
class File {
FILE* fp;
public:
File(const char* name) { fp = fopen(name, "r"); } // Constructor
~File() { if(fp) fclose(fp); } // Destructor
};
Why destructors matter: C++ doesn't have garbage collection. If a constructor allocates memory or acquires a resource, the destructor must release it — otherwise you get memory leaks.
8) Three Types of Constructors
| Type | Syntax | When used |
|---|---|---|
| Default | ClassName(); |
ClassName obj; |
| Parameterized | ClassName(int x); |
ClassName obj(5); |
| Copy | ClassName(const ClassName& other); |
ClassName obj2 = obj1; or pass-by-value |
class Point {
int x, y;
public:
Point() : x(0), y(0) {} // Default
Point(int a, int b) : x(a), y(b) {} // Parameterized
Point(const Point& p) : x(p.x), y(p.y) {} // Copy
};
Why three? Each serves a purpose:
- Default: Creates an object in a known initial state
- Parameterized: Creates an object with specific values
- Copy: Creates a faithful duplicate (critical for pass-by-value and returning objects)
Code Tracing (Questions 9-10)
9) Simple Inheritance
class Animal {
public:
void speak() { cout << "Animal speaks" << endl; }
};
class Dog : public Animal {};
int main() {
Dog d;
d.speak();
return 0;
}
Output:
Animal speaks
Why it works: Dog publicly inherits from Animal. Even though Dog has no members of its own, it inherits all public members of Animal — including speak(). This demonstrates the simplest form of code reuse through inheritance.
The call d.speak() resolves at compile time (not runtime polymorphism — no virtual keyword used).
10) Constructor and Destructor
class A {
public:
A() { cout << "Constructor"; }
~A() { cout << "Destructor"; }
};
int main() {
A obj;
return 0;
}
Output:
ConstructorDestructor
Why it works: When obj is created in main():
- Constructor
A()runs → prints "Constructor" return 0endsmain(),objgoes out of scope- Destructor
~A()runs automatically → prints "Destructor"
Key insight: Destructors are called automatically when objects go out of scope. This is RAII (Resource Acquisition Is Initialization) — resources are cleaned up automatically, preventing leaks.
Applications (Questions 11-13)
11) Rectangle Class
Concepts tested: Encapsulation (private members), parameterized constructors, destructor, member functions.
#include <iostream>
using namespace std;
class Rectangle {
private:
double length;
double width;
public:
// Parameterized constructor
Rectangle(double l, double w) : length(l), width(w) {
cout << "Rectangle created: " << length << " x " << width << endl;
}
// Destructor
~Rectangle() {
cout << "Rectangle destroyed" << endl;
}
double area() const { return length * width; }
double perimeter() const { return 2 * (length + width); }
};
int main() {
Rectangle r1(5.0, 3.0);
Rectangle r2(7.5, 4.2);
cout << "Rectangle 1 — Area: " << r1.area()
<< ", Perimeter: " << r1.perimeter() << endl;
cout << "Rectangle 2 — Area: " << r2.area()
<< ", Perimeter: " << r2.perimeter() << endl;
return 0;
// Destructors called automatically here
}
Why const on member functions: Marking area() and perimeter() as const tells the compiler these functions don't modify the object. This is good practice — it allows calling them on const Rectangle objects and signals intent.
12) Student Class
Concepts tested: Encapsulation with getters/setters, arrays of objects, static/class-level operations.
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name;
int rollNumber;
double marks;
public:
// Setters
void setName(const string& n) { name = n; }
void setRollNumber(int r) { rollNumber = r; }
void setMarks(double m) { marks = m; }
// Getters
string getName() const { return name; }
int getRollNumber() const { return rollNumber; }
double getMarks() const { return marks; }
// Static method to calculate average
static double calculateAverage(Student students[], int count) {
double sum = 0;
for (int i = 0; i < count; i++) {
sum += students[i].marks;
}
return sum / count;
}
};
int main() {
const int NUM_STUDENTS = 3;
Student students[NUM_STUDENTS];
// Set values
students[0].setName("Alice");
students[0].setRollNumber(101);
students[0].setMarks(85.5);
students[1].setName("Bob");
students[1].setRollNumber(102);
students[1].setMarks(92.0);
students[2].setName("Charlie");
students[2].setRollNumber(103);
students[2].setMarks(78.3);
// Display average
double avg = Student::calculateAverage(students, NUM_STUDENTS);
cout << "Average marks: " << avg << endl;
return 0;
}
Why static methods: calculateAverage is static because it operates on the class level — it takes an array of students and computes something, rather than acting on a single student's data. It's called as Student::calculateAverage(...) rather than student.calculateAverage(...).
13) Shape Hierarchy (Polymorphism)
Concepts tested: Pure virtual functions, abstract base classes, runtime polymorphism via virtual functions, array of base pointers.
#include <iostream>
#include <cmath>
using namespace std;
// Abstract base class
class Shape {
public:
virtual double area() const = 0; // Pure virtual
virtual double perimeter() const = 0; // Pure virtual
virtual ~Shape() {} // Virtual destructor for cleanup
};
class Rectangle : public Shape {
private:
double length, width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
double area() const override { return length * width; }
double perimeter() const override { return 2 * (length + width); }
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { return M_PI * radius * radius; }
double perimeter() const override { return 2 * M_PI * radius; }
};
int main() {
// Array of Shape pointers
Shape* shapes[4];
shapes[0] = new Rectangle(5.0, 3.0);
shapes[1] = new Circle(4.0);
shapes[2] = new Rectangle(7.5, 4.2);
shapes[3] = new Circle(2.5);
for (int i = 0; i < 4; i++) {
cout << "Shape " << i+1
<< " — Area: " << shapes[i]->area()
<< ", Perimeter: " << shapes[i]->perimeter() << endl;
}
// Clean up
for (int i = 0; i < 4; i++) {
delete shapes[i];
}
return 0;
}
Why this is the key OOP pattern:
-
= 0makes the functions pure virtual, makingShapean abstract class — you cannot instantiateShapedirectly. It serves only as an interface. -
override(C++11) tells the compiler: "I intend to override a virtual function." If the base class signature changes, the compiler catches it. -
Shape* shapes[4]is an array of base class pointers, but each points to a different derived type (RectangleorCircle). When you callshapes[i]->area(), C++ uses the vtable (virtual table) to dispatch to the correct function at runtime. -
Virtual destructor:
~Shape() {}is virtual so thatdelete shapes[i]correctly calls the derived class destructor (e.g.,~Rectangle()) before the base destructor. Without it, deleting through a base pointer is undefined behavior.
Runtime dispatch (how polymorphism works internally):
shapes[0] → Rectangle object
┌──────────────┐
│ vtable ptr → │──→ Rectangle::area()
│ length=5.0 │ Rectangle::perimeter()
│ width=3.0 │
└──────────────┘
shapes[1] → Circle object
┌──────────────┐
│ vtable ptr → │──→ Circle::area()
│ radius=4.0 │ Circle::perimeter()
└──────────────┘
Key Takeaways
| Concept | C++ Mechanism | When to Use |
|---|---|---|
| Encapsulation | private: members + public methods |
Always — protect internal state |
| Abstraction | Pure virtual functions (= 0) |
Define interfaces that multiple classes implement |
| Inheritance | class Derived : public Base |
"Is-a" relationships; code reuse |
| Polymorphism | virtual functions + base pointers |
Treat different types uniformly |
| RAII | Constructor acquires, destructor releases | Manage resources safely |
| Static methods | static keyword |
Operations on the class, not an instance |
Related
- FAC1003 - Programming II
- FAC1003 Tutorial 14 — OOP & C++ Concepts
- Object-Oriented Programming
- C++ Classes and Objects