Understanding Classes and Objects in C++: A Foundational Guide

Introduction to Object-Oriented Programming

C++ supports object-oriented programming (OOP), which centers around the concept of objects—entities that combine data and behavior. This contrasts with C's procedural approach, where focus lies on functions and step-by-step execution.

Consider laundry as an example:

  • Procedural: Break the task into steps: open machine → add clothes and detergent → select cycle → wait → remove clothes.
  • Object-Oriented: Identify two entities: a person and a washing machine. The person performs actions like opening the machine and adding items; the machine handles the wash cycle internally.

This shift emphasizes collaboration between components rather than linear sequences, leading to better maintainability and scalability.

Defining Classes in C++

In C++, structs can contain both data and functions, effectively beccoming classes. While struct is still compatible with C-style syntax, class is typically preferred for defining new types.

#include <iostream>
#include <cstring>
using namespace std;

class Student {
public:
    void Initialize(const char* name, const char* gender, int age) {
        strcpy(_name, name);
        strcpy(_gender, gender);
        _age = age;
    }

    void Display() const {
        cout << _name << " " << _gender << " " << _age << endl;
    }

private:
    char _name[20];
    char _gender[3];
    int _age;
};

int main() {
    Student s1;
    s1.Initialize("Alice", "Female", 25);
    s1.Display();
    return 0;
}

Member variables are often prefixed with _ or m_ for clarity, though this is not mandatory.

Class Structure and Access Control

A class defines a scope containing members—data (attributes) and functions (methods). Syntax:

class ClassName {
    // members
};

Two common approaches:

  • Define all members inside the class body (often treated as inline).
  • Separate declaration (in .h) from implementation (in .cpp). The latter is recommended for larger projects.

Access specifiers control visibility:

  • public: accessible from outside the class.
  • private: inaccessible externally; default for class.
  • protected: similar to private but relevant in inheritance.

Access scope starts at the specifier and continues until the next one appears. If none follows, it lasts until the closing brace.

Encapsulation: Hiding Complexity

Encapsulation bundles data and methods within a class, exposing only necessary interfaces. It promotes:

  • Security by hiding internal logic.
  • Reusability through well-defined APIs.
  • Simplicity—users interact with public methods without knowing implementation details.

Think of it like a museum: visitors can enter via tickets (public interface), but sensitive artifacts remain protected behind walls (private/protected members).

Class Scope and Member Resolution

Each class creates its own scope. External member definitions must use the scope resolution operator (::):

class Student {
public:
    void Display();
private:
    char _name[20];
    char _gender[3];
    int _age;
};

void Student::Display() {
    cout << _name << " " << _gender << " " << _age << endl;
}

Object Instantiation

Instantiation means creating real instances from a class blueprint. A class itself doesn’t occupy memory—it’s a template. Only when objects are created do they receive space for member variables.

Like architectural plans: the design exists abstractly, but physical buildings (objects) are built from it and occupy real space.

Size of Class Objects

The size of a class object equals the sum of its member variable sizes, adjusted for alignment.

#include <iostream>
using namespace std;

class A1 {
public:
    void func1() {}
private:
    int data;
};

class A2 {
public:
    void func2() {}
};

class A3 {};

int main() {
    cout << sizeof(A1) << endl; // Output: 4 (on most systems)
    cout << sizeof(A2) << endl; // Output: 1 (empty class gets 1 byte)
    cout << sizeof(A3) << endl; // Output: 1
    return 0;
}

Key points:

  • Member functions aren’t stored per instance—they reside in code segment.
  • Empty classes are given 1 byte by the compiler to ensure uniqueness.
  • Alignment rules apply: each member aligns to its natural boundary (min of type size and default alignment, usually 8 bytes).
  • Total size must be a multiple of the largest alignment requirement.

The this Pointer

Every non-static member function implicitly receives a this pointer—a constant pointer to the calling object.

class Date {
public:
    void Print() const {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

    void SetDate(int y, int m, int d) {
        _year = y;
        _month = m;
        _day = d;
    }

private:
    int _year, _month, _day;
};

int main() {
    Date d1, d2;
    d1.SetDate(2024, 7, 14);
    d2.SetDate(2024, 7, 13);
    d1.Print();
    d2.Print();
    return 0;
}

Under the hood, SetDate becomes:

void SetDate(Date* const this, int y, int m, int d)

Properties of this:

  • Type: ClassName* const — cannot change the pointer, but can modify contents.
  • Available only inside member functions.
  • Passed automatically by the compiler; no need for explicit argument.
  • Stored in register (e.g., ECX) or stack depending on optimization.

Edge Case: Null Pointer

Student* ptr = nullptr;
ptr->Display(); // Works if no member access

This works because Display() accesses only data via this, and the function address is shared across all instances.

But:

void Display() const {
    cout << _name << endl; // Accesses member via this
}

Calling ptr->Display() with nullptr causes a crash—dereferencing null leads to undefined behavior.

Thus, while member functions don’t require valid object addresses for their own execution, accessing members through this fails if this is null.

Tags: C++ Classes Objects encapsulation this Pointer

Posted on Mon, 29 Jun 2026 17:55:01 +0000 by blankextacy