Welcome! If you’re just starting with C++, this article will gently introduce you to OOPS in C++ (Object-Oriented Programming System). We’ll use simple language, relatable analogies, and clear code examples. After every section, you’ll find “Try it yourself” challenges to practice what you learn.
Think of OOPS in C++ like building with LEGO: you define blueprints (classes), create pieces from them (objects), and assemble them in smart ways to solve problems efficiently and cleanly.
🔹 What is OOPS in C++?
OOPS in C++ is a programming style that organizes code into classes and objects. Instead of writing everything in one place, code models real-world entities with their data (attributes) and behaviors (methods). This makes programs easier to read, maintain, and reuse.
Core ideas you’ll learn:
- Class & Object: Blueprint vs actual thing.
- Encapsulation: Keeping data safe and tidy.
- Abstraction: Showing only what’s necessary.
- Inheritance: Reusing and extending behavior.
- Polymorphism: One interface, many forms.
Try it yourself
- Write down 3 objects in a room (e.g., Laptop, Chair, Bottle) and list 2 attributes and 2 behaviors for each.
- Identify which details a user needs to see (abstraction) vs what can be hidden.
🔹 Classes and Objects in OOPS
Analogy: A class is like a blueprint for a house. An object is a house built from that blueprint. Many houses (objects) can be built from a single blueprint (class).
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Car { // Class = blueprint
private:
string brand; // Attributes (data members)
int speed{0};
public: // Methods (member functions)
void setBrand(const string& b) { brand = b; }
void accelerate(int delta) { speed += delta; }
void brake(int delta) { speed = max(0, speed - delta); }
void show() const { cout << "Brand: " <<
brand << ", Speed: " << speed << " km/h\n"; }
};
int main() {
Car c1; // Object created from class Car
c1.setBrand("Tesla");
c1.accelerate(30);
c1.show();
c1.brake(10);
c1.show();
return 0;
}
Output
Brand: Tesla, Speed: 30 km/h Brand: Tesla, Speed: 20 km/h
In this example, Car
groups related data and functions together. This is the first step of OOPS in C++: thinking in terms of entities and their behaviors.
Try it yourself
- Create a
Book
class withtitle
,author
, and aprint()
method. - Make two
Book
objects with different values and print them.
🔹 Encapsulation and Access Specifiers (public, private, protected)
Encapsulation means “wrap things up.” Keep sensitive data private and expose safe methods to interact with it. This prevents misuse and keeps the object’s state valid.
#include <iostream>
#include <stdexcept>
using namespace std;
class BankAccount {
private:
double balance; // Hidden data
public:
BankAccount() : balance(0.0) {}
void deposit(double amount) {
if (amount <= 0)
throw invalid_argument("Amount must be positive");
balance += amount;
}
void withdraw(double amount) {
if (amount <= 0 || amount > balance)
throw invalid_argument("Invalid amount");
balance -= amount;
}
double getBalance() const { return balance; } // Safe accessor
};
int main() {
BankAccount acc;
acc.deposit(1000);
acc.withdraw(250);
cout << "Balance: " << acc.getBalance() << "\n";
}
Output
Balance: 750
Here, balance
is private, and the only way to change it is via safe, validated methods. This is the essence of encapsulation in OOPS in C++.
Try it yourself
- Add a method
transferTo(BankAccount& other, double amount)
that moves money safely. - Make invalid operations throw exceptions and test them in
main()
withtry/catch
.
🔹 Constructors and Destructors
Constructors initialize objects when they’re created; destructors clean up when they’re destroyed. Think: “turn the lights on when entering, switch them off when leaving.”
#include <iostream>
#include <string>
#include <utility>
using namespace std;
class FileHandle {
private:
string path;
public:
FileHandle() : path("untitled.txt")
{ cout << "Default opening " << path << "\n"; }
FileHandle(string p) : path(std::move(p))
{ cout << "Opening " << path << "\n"; }
~FileHandle()
{ cout << "Closing " << path << "\n"; } // Destructor
const string& getPath() const { return path; }
};
int main() {
FileHandle f1; // Default constructor
{
FileHandle f2("data.csv"); // Parameterized constructor, auto-closed at scope end
} // f2 destructor runs here
} // f1 destructor runs here
Output
Default opening untitled.txt
Opening data.csv
Closing data.csv
Closing untitled.txt
Try it yourself
- Add a copy constructor that prints when it’s called, then test copying.
- Create a class
Timer
that prints elapsed time in its destructor.
🔹 Abstraction: Show What Matters, Hide the Rest
Abstraction means exposing essential features and hiding unnecessary details. Like a TV remote: buttons are visible, circuitry is hidden.
#include <iostream>
using namespace std;
class TemperatureSensor {
public:
double readCelsius() {
// Internal details hidden: imagine hardware talk here
return 24.5; // Fake reading
}
};
void printComfortStatus(TemperatureSensor& s) {
double t = s.readCelsius(); // Simple, readable interface
cout << "Room temp: " << t <<
" °C - " << (t >= 20 &&
t <= 26 ? "Comfortable" : "Adjust AC") << "\n";
}
int main() {
TemperatureSensor sensor;
printComfortStatus(sensor);
}
Output
Room temp: 24.5 °C - Comfortable
Try it yourself
- Create a
Stopwatch
class with methodsstart()
,stop()
, andelapsed()
but hide timing internals. - Use it in a function that prints “Fast” or “Slow” based on elapsed time.
🔹 Inheritance in C++
Inheritance lets a derived (child) class reuse and extend a base (parent) class—e.g., a Car is a Vehicle with added features.
#include <iostream>
#include <algorithm>
using namespace std;
class Vehicle {
protected:
int speed = 0; // protected: visible to derived classes
public:
void accelerate(int d) { speed += d; }
void brake(int d) { speed = max(0, speed - d); }
void showSpeed() const {
cout << "Speed: " << speed << "\n"; }
};
class Car : public Vehicle { // public inheritance = "is-a"
public:
void honk() const { cout << "Beep!\n"; }
};
int main() {
Car c;
c.accelerate(40); // inherited
c.honk(); // own method
c.showSpeed();
}
Output
Beep! Speed: 40
Try it yourself
- Create
Truck
inheriting fromVehicle
with aload(int kg)
method. - Test both
Car
andTruck
inmain()
.
🔹 Polymorphism: One Interface, Many Forms
Polymorphism lets different objects respond differently to the same call—via overloading at compile time and overriding with virtual
at runtime.
- Compile-time (overloading): same name, different parameters.
- Run-time (overriding): derived class changes base class behavior via
virtual
.
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Animal {
public:
virtual ~Animal() = default;
virtual void speak() const
// virtual for runtime polymorphism
{ cout << "Some sound\n"; }
};
class Dog : public Animal {
public:
void speak() const override { cout << "Woof!\n"; }
};
class Cat : public Animal {
public:
void speak() const override { cout << "Meow!\n"; }
};
int main() {
vector<unique_ptr<Animal>> zoo;
zoo.push_back(make_unique<Dog>());
zoo.push_back(make_unique<Cat>());
for (const auto& a : zoo) {
a->speak(); // Calls Dog/Cat version appropriately
}
}
Output
Woof!
Meow!
Try it yourself
- Add a
Cow
class and overridespeak()
. - Create an overloaded function
area(int r)
andarea(int w, int h)
to illustrate compile-time polymorphism.
🔹 Composition vs Inheritance
Composition means “has-a”—a Car has an Engine; prefer composition when behavior is not a strict “is-a.”
#include <iostream>
using namespace std;
class Engine {
public:
void start() const { cout << "Engine start\n"; }
void stop() const { cout << "Engine stop\n"; }
};
class Car {
private:
Engine engine; // composition
public:
void drive() {
engine.start();
cout << "Driving...\n";
engine.stop();
}
};
int main() {
Car c;
c.drive();
}
Output
Engine start Driving...
Engine stop
Try it yourself
- Create
Computer
with composed classesCPU
,RAM
,Storage
; implementboot()
. - Decide if
Laptop
should inheritComputer
or compose it. Explain why.
🔹 Operator Overloading (Taste of C++ Power)
Classes can define custom behavior for operators like +
or <<
, making usage more intuitive when applied judiciously.
#include <iostream>
using namespace std;
struct Vec2 {
double x{0}, y{0};
// Add two vectors
Vec2 operator+(const Vec2& other) const {
return {x + other.x, y + other.y};
}
};
// Stream output (free function)
ostream& operator<<(ostream& os, const Vec2& v) {
return os << "(" << v.x << ", " << v.y << ")";
}
int main() {
Vec2 a{1, 2}, b{3, 4};
cout << a + b << "\n"; // (4, 6)
}
Output
(4, 6)
Try it yourself
- Overload
-
and==
forVec2
. - Add scalar multiplication:
Vec2 operator*(double k) const
.
🔹 Accessing the Object: this
Pointer and const
Correctness
The this
pointer refers to the current object. Mark methods const
when they don’t modify state—this is a best practice in OOPS in C++.
#include <iostream>
using namespace std;
class Counter {
int value{0};
public:
Counter& increment() { // return *this for chaining
++value;
return *this;
}
int get() const { return value; } // const method
};
int main() {
Counter c;
c.increment().increment();
cout << c.get() << "\n"; // 2
}
Output
2
Try it yourself
- Add a
decrement()
and chain it. - Try making
get()
non-const and see how it blocks calls on const objects.
🔹 Static Members and Utility Functions
static
members belong to the class, not to a specific object. They’re useful for counters and utility methods that don’t depend on instance state.
#include <iostream>
using namespace std;
class Session {
static int liveCount; // declaration
public:
Session() { ++liveCount; }
~Session() { --liveCount; }
static int count() { return liveCount; } // static method
};
int Session::liveCount = 0; // definition
int main() {
cout << Session::count() << "\n"; // 0
Session a, b;
cout << Session::count() << "\n"; // 2
}
Output
0
2
Try it yourself
- Create an
IdGenerator
class withstatic int next()
returning incrementing IDs. - Assign generated IDs to new objects in a class
User
.
🔹 Memory Management and RAII (Smart Pointers)
RAII: Resource Acquisition Is Initialization. Acquire resources in constructors and release them in destructors. Prefer smart pointers (unique_ptr
, shared_ptr
) over raw new/delete
.
#include <iostream>
#include <memory>
using namespace std;
class Widget {
public:
Widget() { cout << "Widget created\n"; }
~Widget() { cout << "Widget destroyed\n"; }
};
int main() {
unique_ptr<Widget> w = make_unique<Widget>(); // auto-cleanup
// no need to delete; destructor runs automatically
}
Output
Widget created
Widget destroyed
Try it yourself
- Create a vector of
unique_ptr<Widget>
and push a few objects. - Experiment with
shared_ptr
by sharing ownership between two variables.
🔹 A Mini OOPS in C++ Project: Shapes and Areas
Combine abstraction, inheritance, and polymorphism to compute areas for different shapes.
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
using namespace std;
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0; // pure virtual → abstract class
virtual const char* name() const = 0;
};
class Circle : public Shape {
double r;
public:
explicit Circle(double r) : r(r) {}
double area() const override { return M_PI * r * r; }
const char* name() const override { return "Circle"; }
};
class Rectangle : public Shape {
double w, h;
public:
Rectangle(double w, double h) : w(w), h(h) {}
double area() const override { return w * h; }
const char* name() const override { return "Rectangle"; }
};
int main() {
vector<unique_ptr<Shape>> shapes;
shapes.push_back(make_unique<Circle>(3.0));
shapes.push_back(make_unique<Rectangle>(4.0, 5.0));
for (const auto& s : shapes) {
cout << s->name() <<
" area: " << s->area() << "\n";
}
}
Output
Circle area: 28.2743
Rectangle area: 20
Try it yourself
- Add a
Triangle
shape with base and height. - Override
name()
properly and print all areas. - Add
perimeter()
as another pure virtual and implement it.
🔹 Best Practices for OOPS in C++
- Prefer composition over inheritance unless there is a true “is-a”.
- Keep data
private
; expose minimal, clear interfaces. - Use
const
for methods and variables whenever possible. - Use smart pointers; avoid manual
new/delete
. - Mark base class destructors
virtual
when using polymorphism. - Follow the Rule of Zero/Three/Five for resource-managing classes.
- Write small, focused classes with single responsibility.
🔹 Common Pitfalls to Avoid
- Forgetting to make base destructors
virtual
(leaks or undefined behavior). - Overusing inheritance where composition is better.
- Exposing internal state directly (breaking encapsulation).
- Not initializing members in constructors (use member initializer lists).
- Missing
override
on overriding functions (harder to catch mistakes).
🔹 Quick Glossary (OOPS in C++)
- Class: Blueprint for objects.
- Object: Instance of a class.
- Encapsulation: Bundling data and methods; hiding details.
- Abstraction: Exposing essential behavior; hiding complexity.
- Inheritance: Deriving a new class from an existing one.
- Polymorphism: One interface, multiple implementations.
- RAII: Acquire in constructor, release in destructor.
🔹 FAQs about OOPS in C++
Q1: What’s the difference between a class and a struct in C++?
By default, class
members are private
; struct
members are public
. Otherwise, they’re nearly identical in C++.
Q2: When should inheritance vs composition be used?
Use inheritance for a clear “is-a” relationship (e.g., Car is a Vehicle). Use composition for “has-a” (e.g., Car has an Engine).
Q3: Why mark functions as virtual
?
To enable runtime polymorphism so calls through base pointers invoke derived implementations. Always add override
on derived methods.
Q4: Do new
and delete
still need to be used?
Prefer smart pointers (unique_ptr
, shared_ptr
) and automatic storage. Use raw new/delete
only when absolutely necessary.
Q5: What is the Rule of Three/Five/Zero?
If a class manages a resource, implement copy constructor, copy assignment, and destructor (Rule of Three). In C++11+, also consider move operations (Rule of Five). If no resources are managed directly, rely on defaults (Rule of Zero).
Q6: Can procedural programming and OOPS in C++ be mixed?
Yes—C++ supports multiple paradigms. Use OOP where it models the problem well, and simple functions where they suffice.
🔹 Wrapping Up
These beginner-friendly examples cover the essentials of OOPS in C++. Keep practicing the “Try it yourself” tasks, and these concepts will become second nature. Happy coding!