CSS Prepare

optional · CSS study notes

4. Data Members of Class

  • Instance data members: each object gets its own separate copy (e.g., length/width in a Rectangle — every object has its own values)
  • Static data members: declared with static, shared across all objects of the class — one copy total, not one per object
  • Initialization: modern C++ prefers initializing data members via the constructor's initializer list (Rectangle(double l, double w) : length(l), width(w) {}) over assigning inside the constructor body — more efficient, and required for const/reference members
class Counter
{
private:
    static int count;   // ONE shared copy across all objects
public:
    Counter() { count++; }
    static int getCount() { return count; }
};
int Counter::count = 0;   // static members must be defined outside the class too

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

Full worked example — static vs instance data members together

#include <iostream>
using namespace std;

class Counter
{
private:
    int id;              // instance member -- each object has its own
    static int count;    // static member -- ONE shared copy for the whole class

public:
    Counter()
    {
        count++;          // increments the SHARED copy
        id = count;        // this object's own personal id
    }

    void show()
    {
        cout << "My ID: " << id << ", Total objects created: " << count << endl;
    }
};

int Counter::count = 0;   // static members MUST be defined outside the class (once, at file scope)

int main()
{
    Counter a, b, c;
    a.show();   // My ID: 1, Total objects created: 3
    b.show();   // My ID: 2, Total objects created: 3
    c.show();   // My ID: 3, Total objects created: 3
    return 0;
}

Notice count is the same value (3) across all three objects — because there's only one shared copy — while id is different for each, since it's an instance member.

Why the initializer list matters

class Point
{
private:
    const int x;   // const member -- MUST be set at construction, can't be assigned later
public:
    Point(int val) : x(val) {}   // initializer list -- the ONLY way to set a const member
    // Point(int val) { x = val; }  -- ERROR: cannot assign to a const member in the constructor body
};

Important MCQs

  1. A static data member is:
    • a) Unique to every object b) Shared as a single copy across all objects of the class c) Only accessible in main() d) Automatically private
  2. Static data members must be:
    • a) Declared only b) Declared inside the class AND defined once outside the class c) Never initialized d) Only used with structs
  3. A const data member can only be initialized via:
    • a) Assignment inside the constructor body b) The constructor's initializer list c) A setter function d) Direct assignment after object creation
  4. In the Counter example, after creating 5 Counter objects, the static count will equal:
    • a) 1 b) 0 c) 5 d) Undefined

✅ Self-Test Questions (with Answers)

Q1. Why must static data members be defined a second time outside the class?

Ans: Because the line inside the class is only a declaration, not a definition. A class definition normally appears in a header that is #included into many translation units, so it must not, by itself, allocate any storage — otherwise every .cpp file including the header would create its own copy of the variable and the linker would report a multiple definition error. The in-class line therefore merely announces "a static member named count of type int exists somewhere"; a single out-of-class definition at file scope actually reserves the storage and gives the linker exactly one symbol to resolve against. Omitting it compiles fine but fails at link time with an undefined reference error — a very commonly tested symptom.

// counter.h
class Counter {
    static int count;          // DECLARATION only -- no memory reserved
public:
    Counter() { count++; }
    static int getCount() { return count; }
};

// counter.cpp  -- exactly ONE translation unit does this
int Counter::count = 0;        // DEFINITION -- allocates the single shared copy

// Without the line above:
//   undefined reference to `Counter::count'      <- LINKER error, not a compiler error

Additional points that earn marks:

  • A static member exists for the whole program's duration even if no object of the class is ever created, which is precisely why its storage cannot depend on instantiation.
  • The definition is written without the static keyword but with the ClassName:: scope qualifier.
  • Exception: a static const member of integral or enum type may be initialised in-class (static const int MAX = 100;), and since C++17 any static member declared inline (or static constexpr, which is implicitly inline) needs no separate definition at all — the inline keyword tells the linker to merge the duplicate definitions.

Q2. Why does a const data member require an initializer list instead of body assignment?

Ans: Because initialisation and assignment are two different operations, and a const object may only ever be initialised. By the time control enters the constructor's { ... } body, every data member has already been constructed; anything written there is an assignment, and assigning to a const member is illegal — the compiler rejects it. The member initializer list (the : x(val) part, which runs before the body) is the only place where the member is still being brought into existence, so it is the only place a const member can be given its value.

class Point {
    const int x;             // const member
    int& ref;                // reference member -- same rule applies
    int y;
public:
    Point(int a, int& r, int b) : x(a), ref(r), y(b) {}   // CORRECT: initialisation

    // Point(int a, int& r, int b) { x = a; ref = r; y = b; }
    //   ERROR: assignment of read-only member 'Point::x'
    //   ERROR: 'ref' must be initialized in the member initialization list
};

The same rule forces the initializer list for:

  • const members — cannot be assigned after construction.
  • Reference members (int&) — a reference must be bound when created and can never be re-seated.
  • Base-class sub-objects and members without a default constructor — there is no default value to overwrite.

Efficiency point (frequently worth a mark): even for ordinary members, the initializer list is preferable. Body assignment default-constructs the member first and then overwrites it — two operations — whereas the initializer list constructs it directly with the right value in one step. MCQ trap: members are initialised in the order they are declared in the class, not in the order written in the initializer list; relying on the written order is a classic source of bugs and compiler warnings.

Q3. In the Counter example, what would count be after creating 5 objects?

Ans: count would be 5. Because count is a static data member, exactly one copy exists for the entire class, and every Counter constructor increments that same shared variable. It starts at 0 (from the out-of-class definition int Counter::count = 0;) and reaches 5 after the fifth construction. Every object then reports the same value for count, while each object's instance member id keeps its own distinct value — the cleanest illustration of "static belongs to the class, non-static belongs to the object."

class Counter {
    int id;                 // instance member: one per object
    static int count;       // static member : ONE for the whole class
public:
    Counter() { count++; id = count; }
    void show() const { cout << "ID " << id << ", total " << count << '\n'; }
    static int getCount() { return count; }   // callable without any object
};
int Counter::count = 0;

int main() {
    Counter a, b, c, d, e;            // five constructions -> count == 5
    a.show();                         // ID 1, total 5
    e.show();                         // ID 5, total 5
    cout << Counter::getCount();      // 5   <- accessed via the CLASS, no object needed
}

Examiner's follow-ups to be ready for: if a destructor decremented count, the value would fall as objects went out of scope, which is the standard way to count live objects rather than total created. Note too that a static member function (getCount()) has no this pointer and therefore cannot read id — it can only touch static members.

📌 Sample & Repeated FPSC Questions (2016–2026)

2026, Paper I, Q4(c) (8 marks) — Demonstrate method overriding using a base class Account and a derived class Current Account. Make balance a protected data member.

class Account {
protected:
    double balance;                 // protected -- so derived classes can touch it directly
public:
    Account(double b) : balance(b) {}
    virtual void withdraw(double amount) {
        if (amount <= balance) balance -= amount;
        else cout << "Insufficient funds.\n";
    }
};

class CurrentAccount : public Account {
    double overdraftLimit;
public:
    CurrentAccount(double b, double odLimit) : Account(b), overdraftLimit(odLimit) {}
    void withdraw(double amount) override {         // overriding -- allows overdraft
        if (amount <= balance + overdraftLimit) balance -= amount;
        else cout << "Withdrawal exceeds overdraft limit.\n";
    }
};

Key reasoning points:

  • balance must be protected (not private) precisely so CurrentAccount can access and modify it directly inside its overridden withdraw() — this is exactly why the question specifies "protected."
  • withdraw() is declared virtual in Account so a call through an Account* correctly dispatches to CurrentAccount::withdraw() at run time — protected data members and method overriding are frequently tested as a pair.

2025, Paper I, Q5(b) (10 marks) — Implement a Book Management System with three classes: Book (book name, ISBN number, publication year), Author (author name, author email), Publisher (publisher name, email, address). Each Book has an Author and a Publisher, created and destroyed with the Book. Explain how composition is used.

class Author {
    string name, email;             // data members -- private
public:
    Author(string n, string e) : name(n), email(e) {}
    ~Author() { /* cleanup */ }
};
class Publisher {
    string name, email, address;    // data members -- private
public:
    Publisher(string n, string e, string a) : name(n), email(e), address(a) {}
};
class Book {
    string bookName, isbn; int year;   // Book's own data members
    Author* author;                    // owned sub-object (composition)
    Publisher* publisher;              // owned sub-object (composition)
public:
    Book(string bn, string i, int y, string an, string ae, string pn, string pe, string pa)
        : bookName(bn), isbn(i), year(y) {
        author = new Author(an, ae);
        publisher = new Publisher(pn, pe, pa);
    }
    ~Book() { delete author; delete publisher; }   // sub-objects destroyed WITH the Book
};

Key reasoning points:

  • bookName, isbn, and year are Book's own data members; author and publisher are pointer-typed data members that Book itself allocates in its constructor and owns exclusively.
  • Composition here means Book has-a Author and has-a Publisher by ownership: their lifetimes are entirely bound to the Book's, since ~Book() explicitly deletes both. That lifetime dependency — not mere containment — is what distinguishes composition from aggregation.

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

MCQs

2026 P-II · Q2. Which of the following statements is true about private data members?

  • (A) They cannot be accessed outside the class, but only through public methods of that class ✅
  • (B) They can be accessed outside the class using objects of that class
  • (C) They can be accessed outside the class
  • (D) None of these
  • Answer: (A).
  • Why: private hides the data inside the class (encapsulation). Outside code can reach it only through the class's public getter and setter methods. Options (B) and (C) describe public members.