CSS Prepare

optional · CSS study notes

6. Classes Advanced (friend, static, composition, this, const)

  • friend: a function or class granted access to another class's private/protected members despite not being a member itself — breaks strict encapsulation deliberately for specific, controlled cases
  • static member: shared across all objects of a class (one copy total), rather than per-object — accessed via the class name, not requiring an instance
  • Object relationships — how classes/objects relate to each other, from loosest to tightest coupling: Association ("uses-a," no ownership), Aggregation ("has-a," weak — the part can exist independently), Composition ("has-a," strong — the part's lifetime is tied to the whole); see the full side-by-side section below for all three with examples
  • this pointer: an implicit pointer available inside non-static member functions, pointing to the calling object itself
  • const: qualifier preventing modification — const member functions cannot modify the object's data members; const parameters/objects cannot be altered

MCQ Trap: static members belong to the class (one shared copy); non-static (instance) members belong to each object separately.

friend — deliberately breaking encapsulation, in a controlled way

class Box
{
private:
    double width;
public:
    Box(double w) : width(w) {}
    friend void printWidth(Box b);   // grants THIS specific function access to private members
};

void printWidth(Box b)
{
    cout << "Width: " << b.width;   // allowed -- printWidth is a friend, even though it's not a member
}

friend is used sparingly — usually for operator overloading (like operator<<) where the function genuinely needs private access but can't be a member function due to argument order.

static — one copy shared by the whole class

class Employee
{
public:
    static int employeeCount;   // shared across ALL Employee objects
    Employee() { employeeCount++; }
};
int Employee::employeeCount = 0;   // defined outside the class

// Employee::employeeCount accessed via CLASS name, not an object
cout << Employee::employeeCount;

Composition — "has-a" using another class as a member

class Engine { public: void start() { cout << "Engine starts\n"; } };

class Car
{
private:
    Engine engine;   // Car HAS-A Engine -- composition, not inheritance
public:
    void start() { engine.start(); }   // delegates to the contained object
};

Object Relationships, Side by Side — Association, Aggregation & Composition

CSS examiners frequently ask you to "distinguish" between these three — memorize this spectrum, from loosest to tightest coupling:

RelationshipTypeOwnership?Lifetime dependencyExample
Association"uses-a" (general link)No ownership either wayIndependent — neither object's lifetime affects the otherA Teacher and a Student in the same School
Aggregation"has-a" (weak)Whole "has" parts, but doesn't own them exclusivelyPart can outlive the wholeA Department has Professors; professors still exist if the department is dissolved
Composition"has-a" (strong)Whole exclusively owns the partsPart is destroyed when the whole is destroyedA House and its Rooms — rooms don't exist independently of the house

Association — the loosest relationship

Two classes interact with or know about each other, but neither owns nor is composed of the other.

class Student;   // forward declaration
class Teacher
{
public:
    void teach(Student& s);   // Teacher USES a Student -- association, no ownership
};

Association can be one-directional (only one class knows about the other) or bidirectional (both classes reference each other), and can carry multiplicity (one-to-one, one-to-many, many-to-many) — e.g., one Teacher teaches many Students.

Aggregation — "has-a," but the part survives independently

The whole holds a reference/pointer to the part, but does not create or destroy it — the part's lifetime is managed elsewhere.

class Professor { public: string name; };

class Department
{
private:
    vector<Professor*> professors;   // Department HAS professors, but doesn't OWN their lifetime
public:
    void addProfessor(Professor* p) { professors.push_back(p); }
    // ~Department() does NOT delete the Professor objects -- they exist independently
};

int main()
{
    Professor p1{"Dr. Khan"};
    Department cs;
    cs.addProfessor(&p1);   // Department just references p1; p1 still exists even if cs is destroyed
}

Composition — "has-a," with exclusive ownership

The whole creates and destroys the part; the part has no independent existence.

class Engine { public: void start() { cout << "Engine starts\n"; } };

class Car
{
private:
    Engine engine;   // Car OWNS an Engine -- composition: Engine is created/destroyed WITH the Car
public:
    void start() { engine.start(); }
};   // when a Car object is destroyed, its Engine member is automatically destroyed too

MCQ Trap (Object Relationships)

  • All three are loosely called "has-a" in casual speech, but strictly: association has no ownership at all, aggregation is a weak "has-a" (the part survives independently), and composition is a strong "has-a" (the part's lifetime is tied to the whole). This three-way distinction — not just "has-a vs is-a" — is what CSS papers actually test.

See also: for the standard UML diagram notation for these same three relationships (hollow diamond = aggregation, filled diamond = composition, plus visibility symbols and inheritance/realization arrows), see the "UML — Class Diagram Notation for OOP" page under "Diagramming Reference" on the Object Oriented Paradigm index.

this pointer — referring to the calling object itself

class Point
{
    int x;
public:
    Point(int x) { this->x = x; }   // "this->x" is the member; plain "x" is the parameter -- this disambiguates
};

const — preventing modification

class Config
{
    int value;
public:
    Config(int v) : value(v) {}
    int getValue() const { return value; }   // const function -- promises not to modify the object
};

const Config c(42);   // const OBJECT -- can only call const member functions on it
c.getValue();          // OK
// c.setValue(10);     // ERROR if setValue() isn't const -- compiler won't allow it

Important MCQs

  1. A friend function:
    • a) Is a member of the class b) Is a non-member function granted access to private/protected members c) Cannot access any class data d) Must be static
  2. operator<< is typically declared as friend because:
    • a) It's faster b) The left operand (e.g., cout) isn't the class itself, so it can't be a member function c) friend functions run at compile time only d) It's required by all operators
  3. Composition represents which type of relationship?
    • a) "is-a" b) "has-a," where a class contains another as a member c) "uses-a" only d) Inheritance
  4. Inside a non-static member function, the this pointer refers to:
    • a) The class itself b) The specific object that invoked the function c) A static member d) Nothing, it's unused in C++
  5. Which relationship involves NO ownership between the two classes at all?
    • a) Composition b) Aggregation c) Association d) Inheritance
  6. In aggregation, if the "whole" object is destroyed, the "part" object:
    • a) Is always destroyed too b) Can continue to exist independently c) Causes a compile error d) Becomes static
  7. Which relationship ties the part's lifetime directly to the whole's (destroying the whole destroys the part)?
    • a) Association b) Aggregation c) Composition d) Overloading

✅ Self-Test Questions (with Answers)

Q1. Why would a class grant friend access instead of just making the data public?

Ans: Because friend is selective, while public is universal. Declaring a member public opens it to every piece of code in the program, permanently destroying encapsulation and freezing the internal representation (any future change breaks unknown callers). A friend declaration grants private access to one named function or one named class only — everyone else is still locked out. Crucially, the class itself chooses its friends: friendship is granted from inside the class and cannot be claimed from outside, so the class retains full control over who may see its internals. friend therefore breaks encapsulation deliberately and in a controlled, documented way, which is very different from abandoning it.

The classic and near-unavoidable case is operator<<. Stream insertion must be written as cout << obj, so its left operand is the ostream, not your class — which means it cannot be a member function of your class, yet it still needs to read the private data:

class Box {
    double width, height;                     // stay PRIVATE to the world
public:
    Box(double w, double h) : width(w), height(h) {}
    friend ostream& operator<<(ostream& os, const Box& b);   // ONE function admitted
};

ostream& operator<<(ostream& os, const Box& b) {
    return os << b.width << " x " << b.height;   // legal: it is a friend
}

int main() {
    Box b(3, 4);
    cout << b << '\n';     // 3 x 4
    // b.width = 99;       // ERROR: 'width' is private -- the rest of the world is still locked out
}

Properties of friendship worth stating in an exam: it is not inherited (a friend of the base is not a friend of the derived class), not transitive ("a friend of my friend is not my friend"), and not reciprocal unless declared both ways. A friend function is not a member: it has no this pointer, is called like an ordinary function, and is not affected by the access specifier under which it is declared. MCQ traps: the friend mechanism is specific to C++ — Java, C# and Python have no equivalent — and "granted private access" must never be confused with "is a member function." Use it sparingly, typically for operator overloads and tightly-cooperating helper classes.

Q2. How is a static member accessed differently from a regular (instance) member?

Ans: A static member belongs to the class, so it is accessed through the class name with the scope-resolution operator — Employee::employeeCount — and needs no object at all; it exists from program start even if not a single object is ever created. An instance member belongs to an object, so it requires an object and the dot or arrow operator — e.id, p->id. (C++ also permits e.employeeCount for a static member, but that is misleading style: the object is ignored entirely, and examiners prefer the ClassName:: form because it shows you understand where the member lives.)

AspectStatic memberInstance member
Belongs toThe classEach object
Copies in memoryExactly one, sharedOne per object
Access syntaxClassName::memberobject.member / ptr->member
Object required?NoYes
LifetimeWhole programLifetime of the object
Has a this pointer?No (static functions)Yes
DefinitionDeclared in class, defined once outsideDeclared in class only
class Employee {
    int id;                                   // instance member
public:
    static int employeeCount;                 // static member: ONE shared copy
    Employee() { employeeCount++; id = employeeCount; }
    static int getCount() { return employeeCount; }   // static function: no 'this'
    // static int bad() { return id; }        // ERROR: no object, so no 'id'
};
int Employee::employeeCount = 0;              // definition, once, at file scope

int main() {
    cout << Employee::getCount();             // 0  -- no object exists yet!
    Employee a, b, c;
    cout << Employee::employeeCount;          // 3  -- via the CLASS
    cout << a.employeeCount;                  // 3  -- legal but poor style
}

Key traps: a static member function has no this pointer, so it can touch only static members — trying to read id from it is a compile error. A static data member must be declared inside the class and defined once outside (or declared inline/constexpr since C++17). And modifying a static member through one object changes what every other object sees, since there is only one copy.

Q3. What's the difference between Car containing an Engine (composition) and Car inheriting from Vehicle (inheritance)?

Ans: They express two different relationships. Composition is "has-a": a Car contains an Engine as a member, so an Engine is a part of a car but is in no sense a kind of car. Inheritance is "is-a": a Car is a Vehicle, a more specific kind of the same thing, so a Car can be used anywhere a Vehicle is expected — this substitutability (the Liskov substitution principle) is what inheritance really buys, and it is what makes run-time polymorphism possible.

AspectComposition (Car has-a Engine)Inheritance (Car is-a Vehicle)
Relationshiphas-a / part-ofis-a / kind-of
Substitutable for the other type?NO — a Car is not an EngineYES — a Car can be used as a Vehicle
Interface inherited?No; Car must delegate explicitlyYes; all public members come along
CouplingLoose — only the Engine public APITight — derived depends on base internals
Changeable at run time?Yes, if held by pointerNo — fixed at compile time
Enables virtual dispatch?NoYes
class Vehicle {                                   // BASE
public:
    virtual void move() { cout << "vehicle moves\n"; }
    virtual ~Vehicle() = default;
};
class Engine { public: void start() { cout << "engine starts\n"; } };

class Car : public Vehicle {   // INHERITANCE: Car IS-A Vehicle
    Engine engine;             // COMPOSITION : Car HAS-A Engine
public:
    void move() override { engine.start(); cout << "car drives\n"; }  // delegation
};

Vehicle* v = new Car;
v->move();       // engine starts / car drives  <- works ONLY because of inheritance
// Engine* e = new Car;   // ERROR: a Car is not an Engine
delete v;

The test to apply in an exam: ask "is X a more specific kind of Y?" → inheritance; "does X contain or own a Y?" → composition. The classic wrong answers are class Library : public Book (a library is not a book) and class Car : public Engine. Note the two coexist naturally, as above. Modern guidance is to favour composition over inheritance, because composition couples classes only through a public interface and can be re-wired at run time, whereas inheritance is permanent and exposes the derived class to the base class's implementation details.

Q4. What does the this pointer resolve inside a constructor like Point(int x) { this->x = x; }?

Ans: this is an implicit pointer, passed as a hidden argument to every non-static member function, that holds the address of the object on which the function was invoked. Inside this constructor the parameter x shadows the data member x — an unqualified x refers to the parameter, because the innermost scope wins. Writing this->x explicitly says "the member x of the object I am constructing," so the statement copies the parameter into the member. Without this->, the line x = x; would assign the parameter to itself, leaving the member uninitialised — a silent bug that compilers warn about (-Wself-assign) but do not reject.

class Point {
    int x, y;
public:
    Point(int x, int y) {
        this->x = x;         // member <- parameter   (disambiguated)
        this->y = y;
        // x = x;            // BUG: parameter assigned to itself; member left as garbage
    }
    // Cleaner: the initializer list has no shadowing problem at all
    // Point(int x, int y) : x(x), y(y) {}

    Point& translate(int dx, int dy) {   // returning *this enables CHAINING
        this->x += dx; this->y += dy;
        return *this;                    // note: *this is the OBJECT, this is its ADDRESS
    }
    void print() const { cout << '(' << x << ',' << y << ")\n"; }
};

Point p(1, 2);
p.translate(3, 0).translate(0, 4).print();   // (4,6)

Other standard uses of this: returning *this from operator= and from setters to allow method chaining; self-assignment checks in a copy assignment operator (if (this == &other) return *this;); and passing the current object to another function (registerObserver(this)). MCQ traps: this is not available in a static member function (there is no invoking object); in a const member function its type is const T*, which is precisely why such a function cannot modify members; and this is a pointer, while *this is the object itself. Best practice is to avoid the shadowing altogether by using the member initializer list, which also works for const and reference members.

Q5. Distinguish association, aggregation, and composition using one original example (not from this page) for each.

Ans: The three form a spectrum of increasing ownership. Take a hospital system for fresh examples:

  • Association ("uses-a", no ownership) — a Doctor consults a Patient. Neither creates nor destroys the other; they merely interact for the duration of a call. Doctors exist without patients and patients without doctors.
  • Aggregation (weak "has-a", part outlives the whole) — a Hospital Ward holds Nurses. If the ward is shut down, the nurses are simply reassigned elsewhere; they were hired by the hospital, not created by the ward.
  • Composition (strong "has-a", part dies with the whole) — a Patient has a MedicalRecord. The record is created when the patient is admitted and is meaningless — and destroyed — once the patient object ceases to exist.
RelationshipHospital exampleOwnershipPart outlives whole?C++ member type
AssociationDoctor consults PatientNoneBoth independentReference or raw pointer parameter
AggregationWard holds NursesShared / externalYESNon-owning pointer or reference member
CompositionPatient has MedicalRecordExclusiveNOValue member or owning unique_ptr
class Patient;
class Nurse    { string name; };
class MedicalRecord { vector<string> notes; };

// ASSOCIATION -- Doctor merely uses a Patient, owns nothing
class Doctor { public: void consult(Patient& p); };

// AGGREGATION -- Ward references nurses created and owned elsewhere
class Ward {
    vector<Nurse*> staff;                       // non-owning
public:
    void assign(Nurse* n) { staff.push_back(n); }
    ~Ward() = default;                          // does NOT delete the nurses
};

// COMPOSITION -- the record is created with, and dies with, the Patient
class Patient {
    MedicalRecord record;                       // value member: exclusive ownership
};                                              // ~Patient() destroys 'record' automatically

The discriminator is always lifetime, never the English word "has". Aggregation and composition are both spoken of as "has-a," so a question phrased only as "has-a" is ambiguous: ask whether destroying the whole must destroy the part. All three differ from inheritance ("is-a"), and design guidance is to favour composition over inheritance for looser coupling.

Q6. In the Department/Professor aggregation example, why doesn't ~Department() delete the Professor objects?

Ans: Because the relationship is aggregation, not composition — Department references the professors but does not own them. The Professor objects were created outside the department (in main(), on the stack) and their lifetimes are managed there; the department merely stores non-owning Professor* pointers. Deleting them in ~Department() would be doubly wrong: it would destroy objects the department never allocated (and, for stack objects, calling delete on a non-heap address is undefined behaviour), and it would leave any other department or list that still points at the same professor holding a dangling pointer.

class Department {
    vector<Professor*> professors;      // NON-OWNING pointers
public:
    void addProfessor(Professor* p) { professors.push_back(p); }
    ~Department() { /* deliberately empty -- we did not allocate, so we do not free */ }
};

int main() {
    Professor p1{"Dr. Khan"};           // created and owned by main()
    {
        Department cs;
        cs.addProfessor(&p1);
    }                                   // cs dies here; p1 is untouched
    cout << p1.name;                    // still valid -- part outlives the whole
}

The governing rule is ownership. Whoever allocates a resource is responsible for releasing it; a class must free only what it itself created. Contrast with composition, where the part is a value member or an owning pointer and the destructor must clean it up:

class Car {
    Engine engine;                            // value member -- destroyed with the Car
    unique_ptr<Gearbox> gb{make_unique<Gearbox>()};  // owning pointer -- also freed automatically
};                                            // no hand-written destructor needed at all

Exam-worthy point: in modern C++ the ownership intent is made visible in the type itself — a raw pointer or a reference signals aggregation (non-owning), while a value member or unique_ptr signals composition (owning). Using unique_ptr for composition also means you never write a destructor, which avoids the rule of three trap of a hand-written destructor without a matching copy constructor and copy assignment operator.

📌 Sample & Repeated FPSC Questions (2016–2026)

2026, Paper I, Q4(b) (6 marks) — A class library contains many book objects. Should this be modeled using inheritance or composition? Why?

This should be modeled using composition, not inheritance. Inheritance models an "is-a" relationship — but a Library is not a kind of Book; it simply contains/owns many Book objects, a "has-a" relationship, which is exactly what composition represents.

class Book { /* title, author, isbn, etc. */ };
class Library {
    vector<Book> books;             // composition: Library HAS-A collection of Books
public:
    void addBook(const Book& b) { books.push_back(b); }
};

Key reasoning points:

  • Using inheritance here (class Library : public Book) would be conceptually wrong — a Library doesn't behave as a Book — and impractical, since inheritance models one conceptual type per relationship, not a collection of many independent objects.
  • Reusable exam template for any "is this is-a or has-a" question: ask "is X a more specific kind of Y?" (→ inheritance) versus "does X contain/own Y?" (→ composition).

2025, Paper I, Q3(b) (6 marks) — State the difference between const keyword and static keyword in C++. Illustrate each keyword with suitable examples.

const means a variable/object/pointer/member function will not modify its value or state after initialization — compiler-enforced read-only access. static gives a variable one persistent storage location for the program's entire lifetime (instead of being recreated each call), or, for a class member, makes it shared by all objects of the class rather than existing per-object.

const int MAX = 100;                        // cannot be reassigned
class Circle { public: double getRadius() const; };   // const member function
class Employee { public: static int count; };          // one copy shared by ALL objects
int Employee::count = 0;

In short: const is about immutability of a value/behaviour; static is about storage duration/sharing — different problems, and they can be combined (static const int LIMIT = 10;).

Key reasoning points:

  • This exact "state the difference + illustrate" phrasing is a favorite FPSC format — always give one code example per keyword, not prose alone.

2018, Paper I, Q4(d) (5 marks) — Consider the classes: Car, SteeringWheel, Vehicle, Van, Minivan, AudioSystem, ParkingLot. Describe all is-a and has-a relationships between them, including an inheritance hierarchy.

  • Inheritance (is-a): Car is-a Vehicle; Van is-a Vehicle; MiniVan is-a Van (and transitively is-a Vehicle).
  • Composition (has-a): Vehicle has-a SteeringWheel; Vehicle has-a AudioSystem; ParkingLot has-a Vehicle (holds many parked vehicles).

Key reasoning points:

  • The trick in this question is that MiniVan sits two levels deep (MiniVan is-a Van is-a Vehicle) — it is not a direct child of Vehicle.
  • ParkingLot's relationship to Car/Van/MiniVan is has-a (it contains vehicles), not is-a — a common slip is trying to make ParkingLot inherit from Vehicle.
  • The empty cells matter too: SteeringWheel and AudioSystem have no relationship to each other — examiners specifically check that you don't over-claim relationships that don't exist.

📜 Past-Paper Questions & MCQs (CSS 2016–2026)

2025 · Paper-I · Q3(b) · 6 marks

Q. State the difference between the const keyword and the static keyword in C++. Illustrate each with examples.

Ans.

Basisconststatic
MeaningThe value cannot be changed after initialisationOne copy shared by all objects (class member) or kept for the whole program (local or global)
On a member functionvoid show() const promises not to modify the objectstatic int count() can be called without an object and accesses only static members
LifetimeNormal for the variableWhole program
const float PI = 3.14159;          // cannot change
class Student {
    static int count;              // shared by all objects
public:
    Student() { count++; }
    static int getCount() { return count; }
    int id() const { return 7; }   // const member function
};
int Student::count = 0;

2025 · Paper-I · Q5(b) · 10 marks

Q. Implement a Book Management System with three classes (Book, Author, Publisher). Each book has an author and a publisher that are created and destroyed with the book. Explain how composition is used.

Ans.

#include <iostream>
#include <string>
using namespace std;
class Author {
    string name, email;
public:
    Author(string n, string e) : name(n), email(e) {}
    void show() { cout << "Author: " << name << " (" << email << ")\n"; }
};
class Publisher {
    string name, email, address;
public:
    Publisher(string n, string e, string a) : name(n), email(e), address(a) {}
    void show() { cout << "Publisher: " << name << ", " << email << ", " << address << "\n"; }
};
class Book {
    string bookName, isbn; int year;
    Author    *author;                  // owned by the book
    Publisher *publisher;               // owned by the book
public:
    Book(string b, string i, int y, string an, string ae, string pn, string pe, string pa)
        : bookName(b), isbn(i), year(y) {
        author = new Author(an, ae);              // created with the book
        publisher = new Publisher(pn, pe, pa);
    }
    ~Book() { delete author; delete publisher; }  // destroyed with the book
    void show() {
        cout << bookName << " | ISBN " << isbn << " | " << year << "\n";
        author->show(); publisher->show();
    }
};
int main() {
    Book *b = new Book("C++ Primer", "978-1", 2023, "Ali", "ali@x.com", "ABC Press", "info@abc.com", "Lahore");
    b->show();
    delete b;                           // author and publisher are deleted as well
}
  • Composition is a strong "has-a" relationship in which the part cannot exist without the whole. The Book creates its Author and Publisher in its constructor and deletes them in its destructor, so their lifetimes match the book.

2026 · Paper-I · Q4(b) · 6 marks

Q. A class library contains many book objects. Should this be modelled using inheritance or composition? Why?

Ans.

  • Composition. A library is not a kind of book (no is-a relationship), but it has many books (has-a relationship).
  • Model: class Library { vector<Book> books; };
  • Reasons:
    • inheritance would give a wrong design, because the library would inherit book behaviour it should not have
    • composition keeps the classes loosely coupled
    • books can be added or removed without changing the class hierarchy
  • Rule of thumb: use inheritance only for "is-a" and composition (or aggregation) for "has-a".

MCQs

2025 P-I · Q7. Which programming language uses the concept of friend function?

  • (A) Python
  • (B) Java
  • (C) C#
  • (D) None of these ✅
  • Answer: (D) None of these.
  • Why: the friend keyword (a function or class that may access another class's private members) exists in C++. Python, Java and C# have no friend functions. Java and C# use package-level or internal access instead.