+ Internal Workings of the C++ Class Construct (Data Members, Access Control, and RAII)

C++ Class Construct  «Prev  Next»
Lesson 1

Internal Workings of the C++ Class Construct

The C++ class is the language construct that makes object-oriented programming possible. A class binds data and the functions that operate on that data into a single unit, giving the programmer control over how state is created, accessed, modified, and destroyed. Every C++ program that goes beyond trivial procedural code depends on this mechanism.

In this module we build classes from the ground up. We begin with the simplest possible examples — a class with one data member and two member functions — and progressively introduce the features that make classes powerful: access control, constructors and destructors, external member function definitions, inline expansion, and the scope resolution operator. By the end of the module you will be able to design classes that are well-encapsulated, correctly initialized, and cleanly separated into header and source files.

Before writing any code, it helps to understand what a class actually is at the language level, what the compiler does with it, and why this design has endured for over four decades.

What Is a Class?

A class is a user-defined type. When you write class Person { ... };, you are telling the compiler that Person is a new type with its own size, layout, and set of legal operations — just as int and double are types with their own sizes and operations. The compiler uses the class definition to determine how much memory an object requires, where each data member sits within that memory, and which functions may be called on the object.

A class definition has two parts: the declaration, which lists the data members and member function prototypes, and the definitions of those member functions, which may appear inside the class body or outside it. This separation of interface from implementation is one of the foundational principles of C++ design, and it scales from small educational programs all the way to distributed object systems where the interface is published across a network boundary while the implementation executes on a remote server.

Data Members and Member Functions

Data members are the variables declared inside a class. They represent the state of each object. Member functions are the functions declared inside a class. They define the operations an object can perform and, crucially, they have access to the object's private data.

Placing data and functions together into a single entity is the central idea of object-oriented programming. Rather than passing a data structure to a collection of free functions, you ask the object itself to perform the operation. The object knows its own state and can protect it from invalid modifications.

This relationship is illustrated in Figure 3-1.
Figure 3-1: Data and Functions
Figure 3-1: Data and Functions

A Simple Class: smallobj

The following program defines a class called smallobj with one private data member and two public member functions. Although it is minimal, it demonstrates the syntax and general features of every C++ class: a data member that holds state, a function that sets the state, and a function that displays it.


// smallobj.cpp
// demonstrates a small, simple object
#include <iostream>
using namespace std;

class smallobj {
private:
    int somedata;              // class data

public:
    void setdata(int d) {      // member function to set data
        somedata = d;
    }
    void showdata() {          // member function to display data
        cout << "Data is " << somedata << endl;
    }
};

int main() {
    smallobj s1, s2;           // define two objects of class smallobj
    s1.setdata(1066);          // call member function to set data
    s2.setdata(1776);
    s1.showdata();             // call member function to display data
    s2.showdata();
    return 0;
}
 

The C++ Programming Language
When the program runs, s1 and s2 are two separate objects. Each has its own copy of somedata, so setting s1 to 1066 and s2 to 1776 does not create a conflict. Both objects share the same member function code — the compiler does not duplicate the function body for each object. Instead, each call implicitly passes a pointer to the calling object (the this pointer), which is how the function knows which object's data to access.

Notice that somedata is declared private. Code outside the class cannot read or modify it directly. The only way to interact with the data is through the public member functions setdata and showdata. This is encapsulation — the class controls its own invariants.

Access Modifiers

C++ provides three access modifiers that control visibility:

public — members are accessible from any part of the program. Public member functions form the class's interface: the set of operations that external code is allowed to use.

private — members are accessible only within the class itself. Data members are almost always private. This forces all access through member functions, which can validate inputs and maintain invariants.

protected — members are accessible within the class and within any class that derives from it. Protected access is relevant when you design class hierarchies for inheritance, a topic covered in a later module.

If you do not specify an access modifier, the default for a class is private. This is the only behavioral difference between class and struct in C++: a struct defaults to public.

Constructors and Destructors

A constructor is a special member function that the compiler calls automatically when an object is created. It has the same name as the class and no return type. Its primary purpose is to initialize the object's data members so that the object is in a valid state from the moment it exists.

A destructor is called automatically when an object goes out of scope or is explicitly deleted. It has the same name as the class preceded by a tilde (~). Its purpose is to release any resources the object acquired during its lifetime — closing files, freeing memory, releasing network connections.

Together, constructors and destructors implement the RAII (Resource Acquisition Is Initialization) idiom: resources are acquired in the constructor and released in the destructor. RAII is one of the most important patterns in C++ because it guarantees cleanup even when exceptions are thrown. Modern C++ extends this principle through smart pointers (std::unique_ptr, std::shared_ptr) which manage heap-allocated memory automatically.


class FileLogger {
public:
    FileLogger(const std::string& path)
        : stream_{path, std::ios::app}
    {
        if (!stream_.is_open())
            throw std::runtime_error("Cannot open log file: " + path);
    }

    ~FileLogger() {
        // stream_ closes automatically (RAII)
    }

    void log(const std::string& message) {
        stream_ << message << '\n';
    }

private:
    std::ofstream stream_;
};
 


In this example, the constructor opens a file and the destructor lets std::ofstream's own destructor close it. There is no manual close() call — the resource is tied to the object's lifetime. If a constructor is not provided, the compiler generates a default constructor that default-initializes each data member. If the class manages resources, you should always provide an explicit constructor and destructor.

Member Function Definitions: Inside vs. Outside

Member functions can be defined in two places. When defined inside the class body, they are implicitly inline — the compiler may expand the function's code at the call site rather than generating a function call. When defined outside the class body using the scope resolution operator (::), they are external member functions and are not implicitly inline.

The choice between internal and external definitions is primarily an organizational one. Short, simple functions (getters, setters, trivial computations) are often left inside the class. Longer functions are defined externally, typically in a separate .cpp source file. This separation keeps the class declaration readable and enables independent compilation — changing a function body in the .cpp file does not force every translation unit that includes the header to recompile.


// Rectangle.h
class Rectangle {
public:
    Rectangle(int width, int height);
    int area() const;
    void setValues(int width, int height);

private:
    int width_;
    int height_;
};

// Rectangle.cpp
#include "Rectangle.h"

Rectangle::Rectangle(int width, int height)
    : width_{width}, height_{height} {}

int Rectangle::area() const {
    return width_ * height_;
}

void Rectangle::setValues(int width, int height) {
    width_  = width;
    height_ = height;
}
 

The Rectangle class above declares three member functions in the header. All three are defined externally in the source file using the Rectangle:: qualifier. The constructor uses a member initializer list (: width_{width}, height_{height}), which is the preferred way to initialize data members in modern C++ because it avoids default-constructing the member first and then assigning to it.

This internal-versus-external distinction is explored in depth in the next lesson on External Member Functions.

Objects: Instances of a Class

A class definition does not create any objects — it only describes a type. To use a class, you create an instance (an object) of that type. Each object gets its own copy of the data members, allocated either on the stack (for local variables) or on the heap (via new or smart pointers). All objects of the same class share the same member function code.


Rectangle r1{10, 5};          // stack allocation
Rectangle r2{20, 3};          // separate object, separate data

auto r3 = std::make_unique<Rectangle>(7, 4);  // heap allocation (RAII)

std::cout << r1.area() << '\n';    // 50
std::cout << r2.area() << '\n';    // 60
std::cout << r3->area() << '\n';   // 28
 

The dot operator (.) accesses members on stack-allocated objects. The arrow operator (->) accesses members through a pointer, including smart pointers. Both operators are used constantly in C++ code, and understanding when to use each is part of understanding object lifetime and ownership.

Looking Ahead: Inheritance and Polymorphism

Once you understand how a single class works internally, the next conceptual leap is combining classes through inheritance. A derived class inherits the data members and member functions of a base class, then extends or overrides them. When a base-class function is declared virtual, the runtime dispatches calls to the correct derived-class override based on the actual type of the object — this is polymorphism.

These topics are covered in the module on virtual functions and reusable design. For now, the key takeaway is that the class construct you are learning in this module is the foundation on which inheritance, polymorphism, and all advanced C++ design patterns are built.

Modern C++ and the Class Construct (C++20 / C++23)

The class construct has evolved significantly in recent C++ standards while preserving backward compatibility with code written decades ago.

C++20 Modules. The traditional header/source split for separating class declarations from definitions has a modern successor: modules. A module interface unit (.cppm) exports the class declaration, and a module implementation unit contains the external definitions. The compilation model changes — no more include guards, no more repeated header parsing — but the fundamental class construct remains identical.

C++20 Concepts and Constrained Templates. Classes can now participate in concept-constrained templates, allowing the compiler to produce clear error messages when a type does not meet a template's requirements. This makes generic programming with class types safer and more readable.

C++23 constexpr Expansion. More class member functions can now be evaluated at compile time. A constexpr constructor combined with constexpr member functions allows entire objects to be created and used during compilation, enabling compile-time validation and lookup tables with zero runtime cost.

C++23 Deducing this. The explicit object parameter feature (this auto&& self) allows a single member function definition to handle both const and non-const objects, reducing boilerplate in classes that previously required duplicate overloads.

Distributed Objects. The encapsulation that a class provides — a public interface hiding private implementation — maps directly onto distributed object architectures. In systems built on CORBA, gRPC, or modern C++ RPC frameworks, a proxy class on the client side exposes the same member function signatures as the real class on the server side. The proxy's member function implementations marshal arguments, send them across the network, and unmarshal the response. From the caller's perspective, the proxy behaves like a local object. The class construct makes this transparency possible because it separates the interface (what you call) from the implementation (how it executes).

Module Roadmap

This module proceeds through the following lessons:

Lesson 1 (this page) — introduces the class construct, data members, member functions, access modifiers, constructors, destructors, and the distinction between internal and external definitions.

Subsequent lessons explore external member functions and the scope resolution operator, explicit inline functions, the this pointer, static members, and practical class design exercises. Each lesson builds on the concepts introduced here, so ensure you are comfortable with the material on this page before moving forward.

SEMrush Software