C++ OOP Fundamentals: Classes, Objects, and Encapsulation

C++ was originally called “C with Classes” because its primary purpose was to add Object-Oriented Programming (OOP) features to the C language. Here is a detailed breakdown of these core object-oriented features.

1. Core OOP Concepts

Classes and Objects

  • Class: A user-defined data type that acts as a blueprint or template for creating objects. It defines data (attributes) and functions (behavior) grouped together.
  • Object: An instance of a class. When a class is defined, no memory is allocated, but memory is allocated when an object is created.

Data Hiding vs. Encapsulation

While often used interchangeably, they are distinct concepts:

  • Encapsulation: The process of bundling data (data members) and the methods that operate on them (member functions) into a single unit (a class).
  • Data Hiding: The mechanism of isolating data inside a class to prevent direct access from outside code, usually achieved using the private access specifier.

Abstraction

Abstraction is the act of representing essential features without including the background details or explanations. In C++, this is achieved using classes, header files, and access specifiers to expose a clean public interface while hiding complex implementation details.

2. Anatomy of a Class

A class consists of Data Members (variables) and Member Functions (methods).

Accessing Class Members

Members are accessed using the dot operator (.) for standard objects, or the arrow operator (->) for object pointers, depending on their Access Specifiers:

  • public: Accessible from anywhere outside the class.
  • private: Accessible only from within the class (default for classes).
  • protected: Accessible within the class and its inherited child classes.
#include <iostream>
using namespace std;

class Box {
private:
    double width; // Data Member (Hidden)

public:
    double length; // Data Member (Accessible)

    // Member Function declared and defined inside the class
    void setWidth(double w) { 
        width = w; 
    }
    
    void displayWidth() {
        cout << "Width: " << width << endl;
    }
};

int main() {
    Box myBox;
    myBox.length = 10.5; // Direct access allowed (public)
    // myBox.width = 5.0;  // ERROR: private member

    myBox.setWidth(5.0);   // Accessing private data via public method
    myBox.displayWidth();
    return 0;
}

3. Special Class Types

Empty Class

A class that has no data members or member functions.

class Empty {};

💡 Note: Even though it is empty, an object of an empty class occupies 1 byte of memory. This ensures that two different objects of the same empty class have distinct memory addresses.

Local Class

A class defined inside a function. It is local to that function block and cannot be accessed outside of it.

  • It cannot contain static data members.
  • It can only access global variables or static variables of the enclosing function.

Global Class

A class defined outside of all functions (usually at the top of the file). It can be used by any function anywhere in the program.

4. The Scope Resolution Operator (::)

The :: operator is used to identify and specify the scope of an identifier. Its primary uses include:

  1. Defining a member function outside the class: Keeps the class definition clean.
  2. Accessing a global variable: Used when a local variable has the same name as a global variable.
  3. Accessing static members: Accessing static variables or functions without creating an object.
  4. Resolving Ambiguity: Used in multiple inheritance to specify which parent class a method belongs to.
#include <iostream>
using namespace std;

int x = 10; // Global x

class Demo {
public:
    void printMessage(); // Function Declaration
};

// Use 1: Defining member function outside class
void Demo::printMessage() {
    cout << "Hello from outside the class definition!" << endl;
}

int main() {
    int x = 5; // Local x
    // Use 2: Accessing global variable
    cout << "Local x: " << x << ", Global x: " << ::x << endl;
    return 0;
}

5. Static Members in a Class

Static Data Members

When a data member is declared static, only one copy of the variable is created for the entire class, regardless of how many objects are instantiated. All objects share this single variable.

  • They must be defined explicitly outside the class using the :: operator.

Static Member Functions

  • Can only access other static data members or static member functions within the class.
  • Can be called using the class name directly (ClassName::functionName()) without creating an object.
#include <iostream>
using namespace std;

class Counter {
public:
    static int count; // Declaration

    Counter() { count++; }
    
    static int getCount() { // Static Function
        return count; 
    }
};

// Explicit Definition required for static data members
int Counter::count = 0; 

int main() {
    Counter c1, c2;
    // Both objects share the same 'count' variable
    cout << "Objects created: " << Counter::getCount() << endl; // Prints 2
    return 0;
}

6. Structure vs. Class in C++

In C++, structures (struct) have been upgraded to behave almost identically to classes, but with one critical distinction regarding default access:

FeatureStructure (struct)Class (class)
Default Access ControlMembers are public by default.Members are private by default.
Default InheritanceInherits as public by default.Inherits as private by default.
Primary IntentTypically used for grouping plain data structures (POD).Used for full object-oriented architecture and encapsulation.

7. Friend Functions and Friend Classes

Data hiding rules state that non-member functions cannot access private or protected members. However, you can bypass this rule using the friend keyword.

Friend Function

A non-member function granted permission to access the private and protected members of a class. It is declared inside the class with the friend keyword but defined outside without it.

Friend Class

When a class is made a friend of another class, all member functions of the friend class gain access to the private and protected members of the target class.

#include <iostream>
using namespace std;

class Alpha {
private:
    int data = 42;

    // Declaring a friend function
    friend void revealAlpha(Alpha obj);
    
    // Declaring a friend class
    friend class Beta; 
};

// Friend function definition (Notice: no Alpha:: scope needed)
void revealAlpha(Alpha obj) {
    cout << "Friend function accessing Alpha: " << obj.data << endl;
}

class Beta {
public:
    void showAlpha(Alpha obj) {
        // Friend class accessing private data seamlessly
        cout << "Friend class accessing Alpha: " << obj.data << endl;
    }
};

int main() {
    Alpha a;
    revealAlpha(a);
    
    Beta b;
    b.showAlpha(a);
    return 0;
}