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
Bookclass withtitle,author, and aprint()method. - Make two
Bookobjects 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 hereOutput
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
Timerthat 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
Stopwatchclass 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
Truckinheriting fromVehiclewith aload(int kg)method. - Test both
CarandTruckinmain().
🔹 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
Cowclass 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
Computerwith composed classesCPU,RAM,Storage; implementboot(). - Decide if
Laptopshould inheritComputeror 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
IdGeneratorclass 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_ptrby 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
Triangleshape 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
constfor methods and variables whenever possible. - Use smart pointers; avoid manual
new/delete. - Mark base class destructors
virtualwhen 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
overrideon 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!