Lesson 2: Member Functions & Arrays of Objects

Lesson Overview

This lesson explores how class behaviors (member functions) are defined, optimized, and nested, followed by how to scale object creation using arrays and manipulate objects by passing them as function arguments (comparing Pass-by-Value with Pass-by-Const-Reference).


πŸš€ 1. The β€œWhy” & Concept Breakdown

Inside vs. Outside Class Definitions

In C++, you can define member functions in two locations:

  1. Inside the Class Declaration: Implicitly requests the compiler to treat the function as inline {copying the function body directly to the call site to eliminate call overhead}. This is ideal for lightweight, 1-to-3 line getters/setters.
  2. Outside the Class Declaration: Uses the Scope Resolution Operator :: {membership label telling the compiler which class owns the function}. This standard practice keeps class declarations clean and uncluttered, separating the interface (what the class does) from the implementation (how it does it).

Nesting of Member Functions

A member function can call another member function of the same class directly, without using the dot (.) operator or an object identifier. This is known as nesting {internal function delegation within the same instance}. It is widely used to delegate work to private helper routines.

Inline Optimizations

When a function is marked inline, the compiler attempts to substitute every call site with the actual body of the function during compilation.

  • Pros: Eliminates the CPU overhead of function call setup (stack frame pushing, jump instructions, register swapping).
  • Cons: Over-inlining causes code bloat {swelling executable binary size}, potentially degrading CPU instruction cache efficiency.
  • Note: inline is merely a suggestion; the compiler silently ignores it for functions containing loops, static variables, recursion, or switch blocks.

Arrays of Objects

We can create contiguous arrays of user-defined classes just like primitive types (Book library[50];). Each element in the array represents an independent object holding its own member state on the stack {fast, structured local memory}.

⚑ Functions & Memory: Python vs. C++

  • Outside Definitions: Prototypes declared inside class, defined outside via ClassName::method() to separate interface from implementation.
  • Passing Objects: Python passes references by default. C++ defaults to Pass-by-Value (makes full copy). Use Pass-by-Const-Reference (const ClassName&) for zero-copy performance + read safety.
  • Stack Arrays: Book lib[3]; allocates objects in one contiguous stack block (unlike Python’s list of heap pointers).

πŸ”‘ 2. Keyword & Syntax Dictionary

Keyword / SymbolSyntax ExampleTechnical Definition & Purpose
::void Time::display()Scope Resolution Operator: Tells the compiler that display() belongs to the scope of the Time class.
inlineinline void putTime()Suggests inline expansion for outside-class functions.
Nestingvoid display() { printTag(); }Calling a member function directly from within another member function of the same class.
Array of ObjectsBook library[3];Creates a contiguous segment of class instances, accessed via subscript: library[i].display().
Pass-by-Valuevoid sum(Time t1)Passes a copy of the object. Modifications inside do not affect the original object.
const ClassName&void sum(const Time& t1)Pass-by-Const-Reference: Passes a memory address directly (zero copying cost), while const guarantees read-only safety.

πŸ’» 3. Step-by-Step Code Evolution

Step 2.1: Defining Member Functions Outside the Class

#include <iostream>
using namespace std;
 
class Time {
private:
    int hours;
    int minutes;
 
public:
    // Prototypes (Declarations) inside the class
    void setTime(int h, int m);
    void putTime();
};
 
// Definitions outside the class using the scope resolution operator
void Time::setTime(int h, int m) {
    hours = h;
    minutes = m;
}
 
void Time::putTime() {
    cout << hours << " hours and " << minutes << " minutes" << endl;
}
 
int main() {
    Time t1;
    t1.setTime(2, 45);
    t1.putTime();
    return 0;
}

Step 2.2: Implementing Nesting of Member Functions

#include <iostream>
using namespace std;
 
class Time {
private:
    int hours;
    int minutes;
    // Private helper function
    int convertToMinutes(); 
 
public:
    void setTime(int h, int m);
    void putTime();
};
 
void Time::setTime(int h, int m) {
    hours = h;
    minutes = m;
}
 
int Time::convertToMinutes() {
    return (hours * 60) + minutes;
}
 
void Time::putTime() {
    // NESTING: calling convertToMinutes() directly without an object or dot operator
    int totalMin = convertToMinutes(); 
    cout << hours << " hours and " << minutes << " minutes (Total: " << totalMin << " mins)" << endl;
}
 
int main() {
    Time t1;
    t1.setTime(1, 15);
    t1.putTime(); // Internally nests convertToMinutes()
    return 0;
}

Step 2.3: Optimizing with inline Functions

#include <iostream>
using namespace std;
 
class Time {
private:
    int hours;
    int minutes;
 
public:
    // Defined inside the class: implicitly treated as INLINE
    void setTime(int h, int m) {
        hours = h;
        minutes = m;
    }
    
    void putTime();
};
 
// Explicitly requesting outside definition to be INLINE
inline void Time::putTime() {
    cout << hours << "h " << minutes << "m" << endl;
}

Step 2.4: Managing Arrays of Objects

#include <iostream>
using namespace std;
 
class Time {
private:
    int hours;
    int minutes;
 
public:
    void setTime(int h, int m) {
        hours = h;
        minutes = m;
    }
    void putTime() const {
        cout << hours << " hours and " << minutes << " minutes" << endl;
    }
};
 
const int SIZE = 3;
 
int main() {
    Time agenda[SIZE]; // Array of objects
    
    for (int i = 0; i < SIZE; i++) {
        int h, m;
        cout << "Enter hours and minutes for event " << i + 1 << ": ";
        cin >> h >> m;
        agenda[i].setTime(h, m); // Accessing via index and dot operator
    }
    
    cout << "\n--- Scheduled Events ---" << endl;
    for (int i = 0; i < SIZE; i++) {
        cout << "Event " << i + 1 << ": ";
        agenda[i].putTime();
    }
    return 0;
}

Step 2.5: Passing Objects as Function Arguments (Value vs. Const Reference)

  • Pass-by-Value (Time t1): Copies the entire object byte-by-byte (invoking copy constructors). This is inefficient for large objects.
  • Pass-by-Const-Reference (const Time& t1): Passes only a pointer-sized memory reference (zero copy overhead), with const enforcing read-only safety.
#include <iostream>
using namespace std;
 
class Time {
private:
    int hours;
    int minutes;
 
public:
    void setTime(int h, int m) {
        hours = h;
        minutes = m;
    }
    void putTime() const {
        cout << hours << " hours and " << minutes << " minutes" << endl;
    }
    
    // Receives Time objects as arguments by const reference for optimal performance
    void sum(const Time& t1, const Time& t2); 
};
 
// References are passed without making copies; const protects read-only state
void Time::sum(const Time& t1, const Time& t2) {
    minutes = t1.minutes + t2.minutes;
    hours = minutes / 60;                // Calculate carryover hours
    minutes = minutes % 60;              // Keep remaining minutes
    hours = hours + t1.hours + t2.hours; // Add original hours
}
 
int main() {
    Time T1, T2, T3;
    
    T1.setTime(2, 45);
    T2.setTime(3, 30);
    
    // T3 invokes sum(), passing T1 and T2 by const reference
    T3.sum(T1, T2); 
    
    cout << "T1 = "; T1.putTime();
    cout << "T2 = "; T2.putTime();
    cout << "T3 (Sum) = "; T3.putTime(); // Expected: 6 hours and 15 minutes
    
    return 0;
}

🧠 4. Under-the-Hood Memory Visualization

Stack Memory (Data Segment)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Time agenda[3] Array                   β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ agenda[0]:                         β”‚ β”‚
β”‚ β”‚   [hours = 2]   [minutes = 45]     β”‚ β”‚
β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚ β”‚ agenda[1]:                         β”‚ β”‚
β”‚ β”‚   [hours = 3]   [minutes = 30]     β”‚ β”‚
β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
β”‚ β”‚ agenda[2]:                         β”‚ β”‚
β”‚ β”‚   [hours = 1]   [minutes = 15]     β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚ 
                    β”‚ (Invokes methods)
                    β–Ό
Code Segment (Shared Function Memory)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Time::setTime(int h, int m)            β”‚
β”‚ Time::putTime()                        β”‚
β”‚ Time::convertToMinutes()               β”‚
β”‚ Time::sum(const Time& t1, const Time& t2)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⚠️ 5. The Debugger’s Guide (Common Traps)

Trap 1: Calling Private Nested Functions Outside the Class

class Set {
private:
    void largest();
public:
    void display() { largest(); } // Legal nesting!
};
int main() {
    Set s;
    s.largest(); // ❌ ERROR: 'largest' is private within this context
}

Trap 2: Dot Operator Syntax in Nesting

void Set::display() {
    Set.largest();  // ❌ ERROR: 'Set' is a class, not an object
    largest();      //  CORRECT: Call nested functions directly
}

Trap 3: Missing Class Scope Prefix on Outside Definitions

// Missing Time::
void display() { 
    cout << hours; // ❌ ERROR: 'hours' was not declared in this scope
}
  • The Fix: Always qualify outside definitions: void Time::display() { ... }.

πŸ’¬ 6. Viva Quick-Prep

Q1: What is Nesting of member functions?

Answer: Nesting is when a member function calls another member function of the exact same class directly within its body, without using an object identifier or the dot operator.

Q2: What is the difference between defining a function inside vs. outside a class?

Answer: Functions defined inside a class are automatically treated as inline candidates by the compiler. Functions defined outside require the scope resolution operator :: and are not inlined unless explicitly prefixed with inline.

Q3: Why is pass-by-const-reference preferred over pass-by-value for objects?

Answer: Pass-by-value copies the entire object, incurring copy constructor overhead. Pass-by-const-reference passes only a memory address (fast) while the const qualifier ensures the function cannot modify the original object.


πŸ“ 7. Practice Exercises & Solutions

Exercise 7.1: KUET Lab Test 01 (ECE 2k23) β€” Library Book Manager

Problem Statement: Define a class Book containing private bookID (int) and availability ('A' for available, 'I' for issued), along with public bookTitle and authorName. Implement assignInitialValues(), issueBook(), returnBook(), and displayBookDetails(). In main(), create an array of 3 Book objects and test state transitions.

#include <iostream>
#include <string>
 
using namespace std;
 
class Book {
private:
    int bookID;
    char availability; // 'A' = Available, 'I' = Issued
 
public:
    string bookTitle;
    string authorName;
 
    void assignInitialValues(int id, string title, string author, char status) {
        bookID = id;
        bookTitle = title;
        authorName = author;
        availability = status;
    }
 
    void issueBook() {
        if (availability == 'A') {
            availability = 'I';
            cout << "Success: Book \"" << bookTitle << "\" has been issued." << endl;
        } else {
            cout << "Error: Book \"" << bookTitle << "\" is already ISSUED!" << endl;
        }
    }
 
    void returnBook() {
        availability = 'A';
        cout << "Success: Book \"" << bookTitle << "\" is now returned and available." << endl;
    }
 
    void displayBookDetails() const {
        cout << "Title: " << bookTitle 
             << " | Author: " << authorName
             << " | Status: " << (availability == 'A' ? "Available" : "Issued") << endl;
    }
};
 
int main() {
    Book library[3];
 
    cout << "=== Registering Books ===" << endl;
    library[0].assignInitialValues(101, "Data Structures", "Lipschutz", 'A');
    library[1].assignInitialValues(102, "Digital Logic", "Morris Mano", 'A');
    library[2].assignInitialValues(103, "C++ Primer", "Lippman", 'I');
 
    cout << "\n--- Initial Status ---" << endl;
    for (int i = 0; i < 3; i++) library[i].displayBookDetails();
 
    cout << "\n--- Testing Operations ---" << endl;
    library[0].issueBook(); // Becomes 'I'
    library[0].issueBook(); // Triggers error: already issued
    library[2].returnBook(); // Becomes 'A'
 
    cout << "\n--- Final Status ---" << endl;
    for (int i = 0; i < 3; i++) library[i].displayBookDetails();
 
    return 0;
}

Exercise 7.2: Library Catalog with Nested Helper Function

Problem Statement: Design a class BookItem that uses an outside-defined nested private helper function isExpensive() returning true if price > 500. When display() is called, it nests isExpensive() to append a [PREMIUM] badge.

#include <iostream>
#include <string>
using namespace std;
 
class BookItem {
private:
    string bookTitle;
    float price;
    bool isExpensive(); // Nested private helper
public:
    void input(string title, float p);
    void display();
};
 
void BookItem::input(string title, float p) {
    bookTitle = title;
    price = p;
}
 
bool BookItem::isExpensive() {
    return price > 500.0f;
}
 
void BookItem::display() {
    cout << "Title: " << bookTitle << " | Price: $" << price;
    if (isExpensive()) { // Nesting call directly
        cout << " [PREMIUM]";
    }
    cout << endl;
}
 
int main() {
    BookItem books[2];
    books[0].input("Standard Math", 250.0f);
    books[1].input("Advanced AI Handbook", 850.0f);
 
    for (int i = 0; i < 2; i++) books[i].display();
    return 0;
}