C++ tutorials  /  Classes and Objects in C++
Chapter 10 · C++

Classes and Objects in C++

A class in C++ is a user-defined data type that serves as a blueprint for creating objects, encapsulating data members (attributes/state) and member functions (methods/behavior) into a single logical unit. An object is a concrete instance of a class, occupying actual memory and holding real values for the data members defined by that class, created either on the stack, the heap (via 'new'), or as a global/static variable.

If a class is a cookie cutter, an object is the actual cookie made from it. You can use the same cookie cutter (class) to stamp out as many cookies (objects) as you want, and while they all share the same shape and structure defined by the cutter, each individual cookie is separate — decorating one doesn't change the others. In C++, once you define a class with 'class ClassName { ... };', you use it to create objects with statements like 'ClassName obj;', and each object gets its own independent copy of the class's data members while sharing the same member functions.

A hotel booking system defines a 'Room' class with data members like roomNumber, price, and isBooked, and methods like bookRoom() and checkAvailability(). The hotel then creates hundreds of 'Room' objects (room101, room102, ...), each independently tracking its own booking status while all sharing the exact same behavior defined once in the class. An e-commerce inventory system similarly defines a 'Product' class, then creates one object per item in the catalog — thousands of distinct product objects all built from the same class blueprint, each with its own name, price, and stock count, but all capable of calling the same 'applyDiscount()' method defined just once in the class.

As programs grow to model dozens or hundreds of related real-world entities (rooms, products, employees, accounts), representing each one with scattered individual variables becomes unmanageable and error-prone. Classes let developers define an entity's structure and behavior exactly once, then instantiate as many independent objects from that definition as needed, ensuring consistency (every object automatically has the same set of attributes and capabilities), reducing code duplication, and making large systems dramatically easier to reason about, extend, and maintain over time.

  • Access Specifiers: private, public, protected: Keywords that control the visibility and accessibility of a class's members from outside the class: 'private' members are accessible only within the class itself (default for classes), 'public' members are accessible from anywhere the object is visible, and 'protected' members are accessible within the class and its derived classes but not from outside code.
  • Member Functions Defined Inside vs. Outside the Class: Member functions can be defined directly inside the class body (implicitly treated as inline), or declared inside the class and defined outside using the scope resolution operator '::' (e.g., 'ClassName::methodName() { ... }'), which is common practice for separating a class's interface (declaration) from its implementation, especially in larger projects using header and source files.
  • The this Pointer: An implicit pointer available inside every non-static member function that points to the specific object the function was called on, commonly used to disambiguate between a member variable and a parameter with the same name, or to return the current object itself for method chaining.
  • Static Members: Data members or member functions declared 'static' belong to the class itself rather than to any individual object, meaning all objects of the class share exactly one copy of a static data member (useful for tracking class-wide information like a count of created objects), and static member functions can be called without creating an object at all.
  • Arrays of Objects: A collection of multiple objects of the same class stored contiguously, declared similarly to a regular array (e.g., 'ClassName objects[10];'), useful for managing groups of related entities like a roster of Student objects or a fleet of Vehicle objects.
class ClassName { private: dataType member1; protected: dataType member2; public: // Constructor ClassName(dataType val) : member1(val) {} // Member function defined inside the class void setMember1(dataType val) { this->member1 = val; // 'this' disambiguates member from parameter } // Member function declared here, defined outside dataType getMember1(); static int objectCount; // static data member declaration }; // Defining a member function outside the class dataType ClassName::getMember1() { return member1; } // Defining a static data member outside the class (required once) int ClassName::objectCount = 0;
A developer building a library management system needs a 'Book' class that keeps track of how many total Book objects have been created across the entire program (using a static member), demonstrates defining some methods inside the class and others outside using the scope resolution operator, and correctly uses the 'this' pointer to avoid naming conflicts between constructor parameters and data members.
Using the this Pointer and Defining Methods Outside the Class
Demonstrates a class where the constructor parameter shares a name with the data member (requiring 'this' to disambiguate), and a member function defined outside the class body using the scope resolution operator.
cpp
#include <iostream> using namespace std; class Book { private: string title; double price; public: Book(string title, double price) { this->title = title; // 'this->title' is the member, 'title' is the parameter this->price = price; } void display(); // declared here, defined outside the class }; void Book::display() { cout << "Title: " << title << ", Price: $" << price << endl; } int main() { Book b("The Pragmatic Programmer", 39.99); b.display(); return 0; }
Title: The Pragmatic Programmer, Price: $39.99
Since the constructor's parameters ('title', 'price') share the same names as the class's data members, 'this->title' explicitly refers to the object's member variable, while plain 'title' refers to the parameter — without 'this->', the assignment would just set the parameter to itself and never actually update the object's data. The 'display()' method is declared inside the class but defined outside using 'Book::display()', a common pattern for separating declaration from implementation.
Static Data Member to Track Object Count Across All Instances
Shows how a static data member is shared across all objects of a class, incrementing every time a new object is constructed, unlike regular (non-static) data members which are independent per object.
cpp
#include <iostream> using namespace std; class Book { private: string title; public: static int totalBooks; // static member declaration Book(string t) { title = t; totalBooks++; // shared across ALL Book objects } }; int Book::totalBooks = 0; // static member must be defined outside the class int main() { Book b1("C++ Primer"); Book b2("Clean Code"); Book b3("Effective C++"); cout << "Total books created: " << Book::totalBooks << endl; return 0; }
Total books created: 3
'totalBooks' is declared 'static', meaning there is exactly ONE copy of it shared across every 'Book' object, rather than each object getting its own separate copy. Every time the constructor runs (once per object created), it increments this single shared counter. It's accessed using the class name directly ('Book::totalBooks') since it belongs to the class itself, not to any individual object, though it can also be accessed via an object (e.g., 'b1.totalBooks').
  • Forgetting to Define a Static Data Member Outside the Class: Declaring a static data member inside the class (e.g., 'static int count;') is only a declaration — it must also be DEFINED exactly once outside the class (e.g., 'int ClassName::count = 0;'), typically in a .cpp file. Forgetting this definition causes a linker error ('undefined reference'), which confuses beginners since the code compiles fine but fails to link.
  • Confusing Class-Level (static) Members with Per-Object Members: Beginners sometimes expect a regular (non-static) data member to behave like a shared, class-wide value, not realizing each object maintains its own separate copy — modifying one object's regular data member has zero effect on any other object, unlike a static member, which truly is shared across all instances.
  • Making Data Members Public by Default, Bypassing Access Control: In C++, the default access specifier for a 'class' is 'private' (unlike 'struct', where it's 'public'), but beginners often explicitly mark everything 'public' out of convenience, or forget that omitting an access specifier at the very top of the class body defaults to private — leading to either broken encapsulation or confusing compile errors when trying to access members that are unintentionally private.
  • Unnecessary or Incorrect Use of the this Pointer: Beginners sometimes overuse 'this->' even when there's no naming ambiguity (which isn't wrong, just unnecessary verbosity), or misunderstand that 'this' is a pointer (not a reference), forgetting they need to use '->' for member access on 'this' rather than '.' , which is only used for actual objects, not pointers to them.
  • Declaring an Array of Objects Without a Default Constructor Available: Declaring 'ClassName objects[5];' requires the class to have an accessible default constructor (one that takes no arguments), since each of the 5 objects must be initialized somehow at the moment the array is created. If the class only defines a parameterized constructor, this declaration fails to compile, confusing beginners who expect the array to simply reserve space without needing immediate initialization.
  • Explicitly Mark Every Section with an Access Specifier: Even though 'class' defaults to 'private', explicitly write 'private:', 'protected:', and 'public:' labels for every section of the class body, making the intended access level immediately clear to anyone reading the code, rather than relying on implicit defaults that can be easily misread or forgotten.
  • Separate Class Declaration (Header) from Implementation (Source File) in Larger Projects: For anything beyond small example programs, declare the class and its member function prototypes in a '.h' header file, and define the actual member function implementations in a corresponding '.cpp' source file using the scope resolution operator, which improves compile times, code organization, and allows the class's interface to be reused across multiple files.
  • Use Static Members Sparingly and Only for Genuinely Class-Wide Data: Reserve 'static' data members for information that truly belongs to the class as a whole rather than to any individual object (like a running count of instances, or a shared configuration value), since overusing static members can introduce hidden global-like state that makes code harder to reason about and test in isolation.
  • Prefer Member Initializer Lists Over Assignment in Constructor Bodies: Initialize data members using the constructor's initializer list syntax (e.g., 'ClassName(int x) : memberX(x) {}') rather than assigning them inside the constructor body, since initializer lists are more efficient (directly initializing rather than default-constructing then reassigning) and are required for initializing 'const' members and reference members, which cannot be assigned after construction.
What is the default access specifier for members of a class versus a struct in C++?
For a 'class', all members declared before any explicit access specifier default to 'private', meaning they are inaccessible from outside the class unless explicitly marked 'public' or 'protected'. For a 'struct', members default to 'public' instead. This is actually the only fundamental technical difference between 'class' and 'struct' in C++ — both support the exact same features (constructors, member functions, inheritance, etc.), but by convention 'struct' is typically used for simple data-holding types while 'class' is used when encapsulation and more complex behavior are involved.
What is the 'this' pointer, and in what scenarios is it particularly useful?
'this' is an implicit pointer available within every non-static member function, pointing to the specific object instance the function was called on. It's particularly useful for: disambiguating between a member variable and a parameter/local variable sharing the same name (e.g., 'this->balance = balance;'), returning a reference to the current object to enable method chaining (e.g., 'return *this;' in a setter that returns '*this'), and passing the current object's address to another function that expects a pointer to that class type.
What is the difference between a static data member and a regular (non-static, instance) data member?
A regular (instance/non-static) data member gets a completely separate, independent copy allocated for every object created from the class — modifying one object's copy has no effect on any other object's copy of that same member. A static data member, by contrast, exists as exactly ONE shared copy across ALL objects of the class (and even accessible without creating any object at all, via the class name), meaning any modification to it through one object is immediately visible when accessed through any other object of that same class.
Why must a static data member be defined outside the class, in addition to being declared inside it?
The declaration inside the class body (e.g., 'static int count;') merely tells the compiler that this static member exists and specifies its type, but does not actually allocate storage for it, since static members are not tied to any individual object's memory layout. The definition outside the class (e.g., 'int ClassName::count = 0;', typically placed in a single .cpp file) is what actually allocates the storage for that one shared instance of the variable and optionally initializes it — omitting this definition, while allowed to compile, causes a 'linker error' when the program tries to actually use the static member.
Can a static member function access non-static (instance) data members of its class directly? Why or why not?
No, a static member function cannot directly access non-static data members, because static functions are not associated with any particular object instance (they can be called without any object existing at all via 'ClassName::staticFunction()'), and therefore don't have an implicit 'this' pointer to know WHICH object's non-static members to access. A static member function can only directly access other static members, or non-static members if it's explicitly given a specific object (e.g., as a parameter) to operate on.
Create a 'Counter' class with a static data member tracking how many Counter objects currently exist, incrementing in the constructor and decrementing in the destructor, then demonstrate the count changing as objects go in and out of scope.
#include <iostream> using namespace std; class Counter { public: static int activeCount; Counter() { activeCount++; } ~Counter() { activeCount--; } }; int Counter::activeCount = 0; int main() { cout << "Initial count: " << Counter::activeCount << endl; { Counter c1; Counter c2; cout << "After creating 2: " << Counter::activeCount << endl; } cout << "After scope ends: " << Counter::activeCount << endl; return 0; }
Write a 'Point' class with private x and y coordinates, a constructor using the 'this' pointer to set them from same-named parameters, and a method 'setX(int x)' that uses 'this' to update the member and returns '*this' to allow method chaining.
#include <iostream> using namespace std; class Point { private: int x, y; public: Point(int x, int y) { this->x = x; this->y = y; } Point& setX(int x) { this->x = x; return *this; } void display() { cout << "(" << x << ", " << y << ")" << endl; } }; int main() { Point p(1, 2); p.setX(99).display(); return 0; }
Create an array of 3 'Employee' objects (with name and salary data members and a default constructor), assign values to each using a loop-friendly setter method, and print all three.
#include <iostream> using namespace std; class Employee { private: string name; double salary; public: Employee() { name = "Unnamed"; salary = 0.0; } void setDetails(string n, double s) { name = n; salary = s; } void display() { cout << name << ": $" << salary << endl; } }; int main() { Employee employees[3]; employees[0].setDetails("Asha", 55000); employees[1].setDetails("Ravi", 62000); employees[2].setDetails("Meena", 58000); for (int i = 0; i < 3; i++) { employees[i].display(); } return 0; }

Classes are the foundational building block of Object-Oriented Programming in C++, serving as blueprints from which independent objects are instantiated, each with their own copy of instance data but shared access to the class's defined behavior. Understanding access specifiers for controlling encapsulation, the 'this' pointer for disambiguation and method chaining, static members for genuinely class-wide shared state, and the distinction between defining methods inside versus outside the class body are all essential skills for writing well-structured, idiomatic C++ classes. These concepts form the groundwork for the next steps in OOP: constructors/destructors, inheritance, and polymorphism.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.