C++ for C Developers | Lesson 5: Introducing Classes: Structs with Muscles (Encapsulation)

Goal: Understand the class keyword in C++, see how it relates to C structs but adds behavior (member functions) and access control (public/private), introducing the fundamental Object-Oriented Programming (OOP) principle of encapsulation.

1. C Structs: Data Containers

Recall C structs: they are primarily used to group related data items together. Any code can typically access and modify the members directly. Logic that operates on the struct’s data is usually implemented as separate functions.

C
#include <stdio.h>
#include <math.h> // For sqrt

// C struct for a 2D Point
typedef struct {
    double x;
    double y;
} PointC;

// Separate functions to operate on PointC
void print_point(PointC p) {
    printf("PointC(x=%f, y=%f)\n", p.x, p.y);
}

double distance_from_origin(PointC p) {
    return sqrt(p.x * p.x + p.y * p.y);
}

void move_point(PointC *p, double dx, double dy) {
    if (p != NULL) {
        p->x += dx;
        p->y += dy;
    }
}

int main() {
    PointC p1 = {3.0, 4.0}; // Aggregate initialization

    print_point(p1);
    printf("Distance from origin: %f\n", distance_from_origin(p1));

    move_point(&p1, 1.0, -2.0);
    printf("After move: ");
    print_point(p1);

    // Direct access is possible (and sometimes problematic)
    p1.x = -100.0; // We can directly change internal data from anywhere
    printf("After direct modification: ");
    print_point(p1);

    return 0;
}

flowchart TD
    subgraph C_Struct["C Struct (Data Only)"]
        C1["struct PointC { x, y };"]
        C2["Data is public"]
        C3["Functions are separate"]
        C4["Direct access: p.x = -100"]
    end

The C approach keeps data (struct PointC) and behavior (print_point, distance_from_origin, move_point) separate. All data members are exposed.

2. C++ structs: Data + Behavior

In C++, structs are enhanced. They can contain not only data members but also member functions (also called methods) that operate on the struct’s data.

C++
#include <iostream>
#include <cmath> // For std::sqrt

// C++ struct for a 2D Point
struct PointCPP_Struct {
    // Data members
    double x;
    double y;

    // Member functions (Methods) - operate on the struct's data
    void print() {
        // Inside a member function, 'x' and 'y' implicitly refer
        // to the members of the specific object this function is called on.
        std::cout << "PointCPP_Struct(x=" << x << ", y=" << y << ")" << std::endl;
    }

    double distanceFromOrigin() {
        return std::sqrt(x * x + y * y);
    }

    void move(double dx, double dy) {
        x += dx;
        y += dy;
    }
}; // Don't forget the semicolon!

int main() {
    PointCPP_Struct p1 = {3.0, 4.0}; // Aggregate initialization still works

    p1.print(); // Call member function using the dot (.) operator
    std::cout << "Distance from origin: " << p1.distanceFromOrigin() << std::endl;

    p1.move(1.0, -2.0);
    std::cout << "After move: ";
    p1.print();

    // Direct access is still possible by default in a C++ struct
    p1.x = -200.0;
    std::cout << "After direct modification: ";
    p1.print();

    return 0;
}

Notice how the functions are now part of the struct definition and are called on an object (p1.print()). This bundles data and behavior.

3. C++ class: Introducing Access Control

The class keyword provides everything a C++ struct does, but with one key difference in default behavior and a stronger emphasis on encapsulation.

Encapsulation means:

  1. Bundling data (attributes) and methods (behavior) that operate on the data together within a single unit (the class).
  2. Controlling access to the internal state (data members) of that unit, hiding implementation details.

This is achieved using access specifiers:

  • public: Members declared as public are accessible from anywhere (inside the class, or by outside code). This defines the class’s interface.
  • private: Members declared as private are only accessible from within the member functions of the same class. They are hidden from outside code. This protects the internal state.
  • protected: Related to inheritance (Lesson 8). Acts like private, but accessible by derived classes.

The class vs struct Difference:

  • In a class, members are private by default.
  • In a struct, members are public by default.
flowchart LR
    subgraph Struct
        St1["Members public by default"]
        St2["Used for simple data aggregates"]
        St3["Can have methods"]
    end
    subgraph Class
        Cls1["Members private by default"]
        Cls2["Used for encapsulation"]
        Cls3["Interface via public methods"]
    end
 

That’s the only technical difference. By convention, class is used when you want to enforce data hiding and encapsulation, while struct is often used for simpler data aggregates where direct access is acceptable (though C++ structs can have private members and methods too).

Example using class:

C++
#include <iostream>
#include <cmath>

class Point { // Using 'class' keyword
// By default, members here would be private

private: // Explicitly declare members as private (Good practice)
    // Data members are now hidden from direct external access
    double m_x; // Often prefix private members (e.g., m_, _, etc. - convention varies)
    double m_y;

public: // Members from here on are public
    // Provide public methods to interact with the private data

    // Setter methods to modify private data (optional, controls how data changes)
    void setX(double x) {
        // Could add validation here, e.g., if (x >= 0)
        m_x = x;
    }

    void setY(double y) {
        m_y = y;
    }

    // Getter methods to access private data (controls how data is read)
    double getX() const { // 'const' means this method doesn't modify the object's state
        return m_x;
    }

     double getY() const {
        return m_y;
    }

    // Constructor (special method for initialization - covered next lesson!)
    // For now, let's use setters. A default constructor is implicitly generated.

    // Other methods operating on the data
    void print() const { // Also marked const
        std::cout << "Point(x=" << m_x << ", y=" << m_y << ")" << std::endl;
    }

    double distanceFromOrigin() const { // Also marked const
        return std::sqrt(m_x * m_x + m_y * m_y);
    }

    void move(double dx, double dy) { // Not const - it modifies state
        m_x += dx;
        m_y += dy;
    }

}; // Don't forget the semicolon!

int main() {
    Point p1; // Create an instance (object) of the Point class.
              // Uses the default constructor (members likely uninitialized or zeroed)

    // Set values using public methods
    p1.setX(3.0);
    p1.setY(4.0);

    // Access values using public methods
    std::cout << "Initial state: ";
    p1.print();
    std::cout << "X coordinate: " << p1.getX() << std::endl;
    std::cout << "Distance from origin: " << p1.distanceFromOrigin() << std::endl;

    p1.move(1.0, -2.0);
    std::cout << "After move: ";
    p1.print();

    // ILLEGAL! Cannot access private members directly from outside the class
    // p1.m_x = -300.0; // Compile-time Error! 'm_x' is private
    // std::cout << p1.m_y; // Compile-time Error! 'm_y' is private

    // We must use the public interface
    p1.setX(-300.0); // OK, using the public setter method
    std::cout << "After setting X via method: ";
    p1.print();


    // Pointer access
    Point* p_ptr = new Point(); // Allocate on heap
    p_ptr->setX(10.0);          // Use -> operator to access members via pointer
    p_ptr->setY(20.0);
    p_ptr->print();
    delete p_ptr;               // Don't forget to delete (or use smart pointers!)

    return 0;
}

Benefits of Encapsulation:

  • Data Hiding: Protects the internal state of an object from unintended external modification. Prevents the object from being put into an invalid state.
  • Maintainability: The internal implementation of the class (private members, method bodies) can be changed without affecting the code that uses the class, as long as the public interface (public methods) remains compatible.
  • Organization: Keeps data and the operations related to it logically grouped together.
  • Control: Allows the class designer to control exactly how data is accessed and modified (e.g., through getter/setter methods that can include validation).

Summary of Lesson 5:

  • C++ structs can contain both data members and member functions (methods). By default, members are public.
  • C++ classes also contain data members and member functions. By default, members are private.
  • Access specifiers (public, private, protected) control visibility of class members.
  • Encapsulation is the bundling of data and methods within a class and controlling access (usually making data private and providing public methods).
  • Objects are instances of classes. Use . to access members of an object, -> to access members via a pointer to an object.
  • Use classes to implement abstract data types with hidden internal state and a controlled public interface.

Next Lesson: We’ll focus on special member functions: constructors (how objects are initialized) and destructors (how objects are cleaned up), which are crucial for proper object lifecycle management, especially when dealing with resources like dynamic memory.

More Sources:

cppreference – Class Declaration: https://en.cppreference.com/w/cpp/language/class

Learn C++ – Classes Tutorial: https://www.learncpp.com/cpp-tutorial/classes-and-class-members/

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top