C++ Classes and Objects: Complete Guide — Constructors, Destructors, Encapsulation & DSA (2025)
⚡ Quick Answer: Classes and Objects in C++
- class — blueprint combining data + behavior; default access is
private - object — runtime instance of a class, has its own copy of member variables
- Access specifiers —
public,private,protectedcontrol visibility - Constructor — called on creation; types: default, parameterized, copy, move
- Destructor —
~ClassName()called on destruction; release resources here - Encapsulation — private data + public interface = protected invariants
- Rule of Three/Five — if you define a destructor, also define copy/move ops
Classes and objects are the heart of C++ OOP — they are the building blocks for every data structure you'll implement: linked lists, stacks, queues, trees, and graphs. This guide covers everything from the absolute basics to the Rule of Three/Five, with complete annotated examples, a visual class anatomy diagram, and five DSA implementations.
Class Basics
Declaration & syntax
Access Specifiers
public / private / protected
Constructors
All 4 types explained
Destructors
Cleanup & memory
Encapsulation
Getters, setters, this
Rule of Three/Five
Deep copy & move
class vs struct
When to use which
DSA Examples
5 complete classes
Class Declaration, Anatomy & First Object
A class is a user-defined type that bundles data (member variables) and behavior (member functions/methods). By itself a class definition allocates no memory — only when you create an object does memory get allocated.
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name;
int id;
public:
// Parameterized constructor with initializer list
Student(string n, int i) : name(n), id(i) {
cout << "Constructor: " << name << endl;
}
// Destructor
~Student() {
cout << "Destructor: " << name << endl;
}
// Const method — cannot modify *this
void display() const {
cout << "Name: " << name << ", ID: " << id << endl;
}
void setName(string n) { name = n; }
string getName() const { return name; }
};
int main() {
// Stack object — destroyed when scope ends
Student s1("Alice", 101);
s1.display(); // dot operator
// Heap object — destroyed when we delete
Student* s2 = new Student("Bob", 102);
s2->display(); // arrow operator
s2->setName("Robert");
s2->display();
delete s2; // destructor called here
// s1 destructor called here (scope end)
return 0;
}
Constructor: Alice
Constructor: Bob
Name: Alice, ID: 101
Name: Bob, ID: 102
Name: Robert, ID: 102
Destructor: Robert
Destructor: AliceAccess Specifiers: public, private, protected
public
Accessible from anywhere — inside the class, from objects, from derived classes.
Use for: methods, constructors, the public interfaceprivate
Accessible only within the class's own methods. Default for class.
protected
Accessible within the class AND derived (child) classes. Not from outside.
Use for: members derived classes need to extendprivate. Expose what callers
need through public methods. This lets you change the internal representation
without breaking code that uses your class — the definition of a stable API.
Constructors — All 4 Types With Examples
A constructor is a special member function called automatically when an object is created. It has the same name as the class, no return type, and can be overloaded.
MyClass obj;. Compiler auto-generates one if you define no constructors. MyClass() : x(0) {}MyClass(int v) : x(v) {}. Called by MyClass obj(5); or MyClass obj{5};MyClass(const MyClass& o) : x(o.x) {}. Called by MyClass b = a;MyClass(MyClass&& o) : x(o.x) { o.x=nullptr; }. Enables efficient vector resizing.#include <iostream>
using namespace std;
class Point {
public:
double x, y;
// 1. Default constructor
Point() : x(0), y(0) {
cout << "Default ctor: (0,0)\n";
}
// 2. Parameterized constructor
Point(double px, double py) : x(px), y(py) {
cout << "Param ctor: (" << x << "," << y << ")\n";
}
// 3. Copy constructor
Point(const Point& o) : x(o.x), y(o.y) {
cout << "Copy ctor: (" << x << "," << y << ")\n";
}
void print() const { cout << "("<<x<<","<<y<<")\n"; }
};
int main() {
Point p1; // default ctor
Point p2(3.0,4.0); // parameterized
Point p3 = p2; // copy ctor
Point p4(p2); // also copy ctor
return 0;
}
Default ctor: (0,0)
Param ctor: (3,4)
Copy ctor: (3,4)
Copy ctor: (3,4)class Circle {
const double PI; // const member — MUST use initializer list
double& ref; // reference member — MUST use initializer list
double r;
public:
// Initializer list runs BEFORE the constructor body
// Order matches declaration order, not list order!
Circle(double& external, double radius)
: PI(3.14159), // const — can only be init, not assigned
ref(external), // reference — can only be init, not assigned
r(radius) // preferred: direct init, not default+assign
{ }
double area() const { return PI * r * r; }
};
// WITHOUT initializer list these would be compile errors:
// Circle(...) { PI = 3.14; } ← error: assignment to const
// Circle(...) { ref = x; } ← error: reference must be initialized
const and reference members,
initializer lists are mandatory. For all other members, they are preferred:
a member initialized in the list is constructed directly from the value; one assigned in the
body is first default-constructed, then assigned — two operations instead of one.
Destructors and Resource Management
The destructor (~ClassName()) runs automatically when an object is destroyed —
when a stack object leaves scope, or when delete is called on a heap object.
Its job: release any resource the object owns (heap memory, file handles, network connections, locks).
#include <iostream>
using namespace std;
class DynamicArray {
int* data;
int size;
public:
DynamicArray(int n) : size(n), data(new int[n]()) {
// new int[n]() zero-initializes the array
cout << "Allocated " << n << " ints\n";
}
~DynamicArray() {
delete[] data; // [] required for arrays — not delete data
cout << "Freed array of size " << size << "\n";
}
int& operator[](int i) { return data[i]; }
int getSize() const { return size; }
};
int main() {
{ // inner scope
DynamicArray arr(5);
arr[0] = 10; arr[1] = 20;
cout << arr[0] << " " << arr[1] << endl;
} // ← destructor called here automatically
cout << "Back in outer scope\n";
return 0;
}
Allocated 5 ints
10 20
Freed array of size 5
Back in outer scopedelete[] for arrays allocated with new[].
Using delete (without []) on an array is undefined behavior — typically
only the first element's destructor runs and the rest of the memory is leaked. Pair
new T[n] with delete[] and new T with delete.
Better yet: use std::vector<T> which handles this automatically.
Encapsulation, Getters/Setters, const Methods & this
#include <iostream>
#include <stdexcept>
using namespace std;
class BankAccount {
private:
double balance;
string owner;
public:
BankAccount(string owner, double initial)
: owner(owner), balance(initial) {}
// const getter — returns a copy; const objects can call this
double getBalance() const { return balance; }
string getOwner() const { return owner; }
// Validated setter — encapsulation enforces invariant
void deposit(double amount) {
if (amount <= 0) throw invalid_argument("Deposit must be positive");
balance += amount;
}
void withdraw(double amount) {
if (amount > balance) throw runtime_error("Insufficient funds");
balance -= amount;
}
// 'this' used to disambiguate and for method chaining
BankAccount& setOwner(string owner) {
this->owner = owner; // 'this->owner' = member, 'owner' = param
return *this; // enables chaining: acc.setOwner("X").deposit(100)
}
void display() const {
cout << owner << ": $" << balance << endl;
}
};
int main() {
BankAccount acc("Alice", 1000.0);
acc.deposit(500.0);
acc.withdraw(200.0);
acc.display();
// Method chaining using *this
acc.setOwner("Alice Smith").deposit(100.0);
acc.display();
try {
acc.withdraw(99999.0); // throws — balance protected
} catch(const exception& e) {
cout << "Error: " << e.what() << endl;
}
return 0;
}
Alice: $1300
Alice Smith: $1400
Error: Insufficient fundsRule of Three & Rule of Five
When a class manually manages a resource (heap memory, file handle, mutex), the compiler- generated copy/move operations do the wrong thing. The Rule of Three (C++03) and Rule of Five (C++11) tell you when to define your own.
| Special function | Called when | Default behavior | Must define when… |
|---|---|---|---|
| Destructor | Object destroyed | Does nothing | Class owns heap resources |
| Copy constructor | MyClass b = a; | Shallow copy | Shallow copy causes double-free |
| Copy assignment | b = a; (after both exist) | Shallow copy | Same as above |
| Move constructor (C++11) | MyClass b = move(a); | Shallow copy | Performance: transfer, not copy |
| Move assignment (C++11) | b = move(a); | Shallow copy | Same as move constructor |
#include <iostream>
#include <cstring> // memcpy
using namespace std;
class Buffer {
int* data;
int size;
public:
Buffer(int n) : size(n), data(new int[n]()) {}
// 1. Destructor
~Buffer() { delete[] data; }
// 2. Copy constructor — DEEP copy
Buffer(const Buffer& o) : size(o.size), data(new int[o.size]) {
memcpy(data, o.data, size * sizeof(int));
}
// 3. Copy assignment — deep copy + self-assignment guard
Buffer& operator=(const Buffer& o) {
if (this == &o) return *this; // self-assignment guard
delete[] data;
size = o.size;
data = new int[size];
memcpy(data, o.data, size * sizeof(int));
return *this;
}
int& operator[](int i) { return data[i]; }
int getSize() const { return size; }
};
int main() {
Buffer a(3); a[0]=1; a[1]=2; a[2]=3;
Buffer b = a; // copy constructor — DEEP copy
b[0] = 99; // modifying b should NOT affect a
cout << a[0] << endl; // 1 — independent copy ✓
cout << b[0] << endl; // 99
return 0;
}
1
99std::vector, std::unique_ptr, or std::shared_ptr
as member types. These handle deep copying and cleanup automatically — your class can use
the compiler-generated defaults and get correct behavior for free.
class vs struct — When to Use Which
| Feature | class | struct |
|---|---|---|
| Default access | private | public |
| Default inheritance | private | public |
| Supports methods | ✓ Yes | ✓ Yes |
| Supports constructors | ✓ Yes | ✓ Yes |
| Supports inheritance | ✓ Yes | ✓ Yes |
| Convention | Complex objects with encapsulation | Plain data grouping, competitive programming |
// struct: plain data record, no invariants to enforce
struct Point {
double x, y; // public by default — fine for plain data
};
// struct in competitive programming — custom comparator for Kruskal's
struct Edge {
int u, v, w;
bool operator<(const Edge& e) const { return w < e.w; }
};
// class: complex object with invariants — private data, public interface
class BankAccount {
private:
double balance; // invariant: balance >= 0 enforced by methods
public:
void deposit(double a); // validates before modifying
void withdraw(double a); // validates before modifying
};
DSA Applications: 5 Complete Class-Based Data Structures
1. Linked List
#include <iostream>
using namespace std;
class LinkedList {
private:
struct Node { // nested private struct
int data;
Node* next;
Node(int v) : data(v), next(nullptr) {}
};
Node* head;
int sz;
public:
LinkedList() : head(nullptr), sz(0) {}
~LinkedList() { // destructor frees all nodes
while (head) {
Node* t = head;
head = head->next;
delete t;
}
}
void push_front(int v) {
Node* n = new Node(v);
n->next = head;
head = n; sz++;
}
void push_back(int v) {
Node* n = new Node(v);
if (!head) { head = n; sz++; return; }
Node* cur = head;
while (cur->next) cur = cur->next;
cur->next = n; sz++;
}
void display() const {
for (Node* c = head; c; c = c->next)
cout << c->data << " → ";
cout << "null\n";
}
int size() const { return sz; }
};
int main() {
LinkedList lst;
lst.push_back(1); lst.push_back(2); lst.push_back(3);
lst.push_front(0);
lst.display();
cout << "Size: " << lst.size() << endl;
return 0; // destructor frees all nodes
}
0 → 1 → 2 → 3 → null
Size: 42. Stack (array-based)
#include <iostream>
#include <stdexcept>
using namespace std;
template<typename T, int MAX = 100>
class Stack {
T arr[MAX];
int top;
public:
Stack() : top(-1) {}
void push(const T& v) {
if (top == MAX-1) throw overflow_error("Stack full");
arr[++top] = v;
}
T pop() {
if (isEmpty()) throw underflow_error("Stack empty");
return arr[top--];
}
const T& peek() const {
if (isEmpty()) throw underflow_error("Stack empty");
return arr[top];
}
bool isEmpty() const { return top == -1; }
int size() const { return top + 1; }
};
int main() {
Stack<int> s;
s.push(10); s.push(20); s.push(30);
cout << "Top: " << s.peek() << endl;
while (!s.isEmpty()) cout << s.pop() << " ";
cout << endl;
return 0;
}
Top: 30
30 20 10 3. BST Node — struct for competitive programming
#include <iostream>
using namespace std;
struct BST {
int val;
BST* left = nullptr;
BST* right = nullptr;
BST(int v) : val(v) {}
static BST* insert(BST* root, int v) {
if (!root) return new BST(v);
if (v < root->val) root->left = insert(root->left, v);
else root->right = insert(root->right, v);
return root;
}
static void inorder(BST* root) {
if (!root) return;
inorder(root->left);
cout << root->val << " ";
inorder(root->right);
}
};
int main() {
BST* root = nullptr;
for (int x : {5,3,7,1,4,6,8}) root = BST::insert(root, x);
cout << "Inorder: ";
BST::inorder(root);
cout << endl;
return 0;
}
Inorder: 1 3 4 5 6 7 8 Best Practices & Common Mistakes
✅ Private data, public interface
Never expose data members directly. Use methods that validate before modifying — that's the point of encapsulation.
✅ Use initializer lists
Always initialize members in the constructor's initializer list. Required for const and reference members; preferred everywhere else.
✅ Mark read-only methods const
int getX() const — lets const objects and const references call the method. Without const, passing your object as const ref breaks.
✅ Follow Rule of Three/Five
If your class manages heap memory (raw pointer + new), define the destructor, copy constructor, and copy assignment. Better: use std::vector/unique_ptr instead.
✅ Give base classes virtual destructors
If your class is used polymorphically (Base* p = new Derived()), the base destructor MUST be virtual — otherwise only the base destructor runs on delete.
✅ Use smart pointers
unique_ptr and shared_ptr handle destruction automatically — eliminating the need to manually implement the Rule of Three in most cases.
❌ Uninitialized members
Local variables of built-in types (int, double) in an uninitialized object contain garbage. Initialize ALL members in constructors.
❌ delete without delete[]
For int* arr = new int[n], always use delete[] arr. Using plain delete arr is undefined behavior.
Frequently Asked Questions
class defaults to private; struct defaults to public. Both support all OOP features: constructors, destructors, methods, inheritance, and templates. Convention: use struct for plain data aggregation with no invariants to protect; use class when you need encapsulation (private data + validated public interface).const members — cannot be assigned after construction. (2) Reference members — same reason. (3) Member objects without a default constructor. (4) All other members — preferred because it initializes directly rather than default-constructing then assigning (two operations vs one). The initializer list executes before the constructor body in declaration order.this is a pointer to the current object available inside every non-static member function. It's used to: (1) Disambiguate when a parameter has the same name as a member: this->name = name;. (2) Return the current object for method chaining: return *this;. (3) Pass the current object to another function. In const member functions, this is a pointer to const — you cannot modify members through it.virtual ~Base() = default; to any class intended to be used as a base class. Rule: if any method in your class is virtual, the destructor should be virtual too.MyClass obj;) when: the object's lifetime should be tied to the current scope, and the object is of reasonable size. Use heap allocation (new MyClass()) when: the object must outlive the current scope, the object is very large, or you need polymorphism via a base pointer. In modern C++, prefer std::make_unique<MyClass>() over raw new — it prevents leaks even when exceptions are thrown.Build DSA data structures with C++ classes
Structured lessons, 200+ exercises, certificate. Join 50,000+ students on CoodeVerse.