OOPS in C++ Made Easy: Classes, Objects & Examples

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:

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 with title, author, and a print() 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() with try/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 methods start(), stop(), and elapsed() 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 from Vehicle with a load(int kg) method.
  • Test both Car and Truck in main().

🔹 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 override speak().
  • Create an overloaded function area(int r) and area(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 classes CPU, RAM, Storage; implement boot().
  • Decide if Laptop should inherit Computer 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 == for Vec2.
  • 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 with static 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!

Leave a Comment

About RadiantRiva

Your go-to resource for coding tutorials, developer guides, and programming tips.

Learn More

Quick Links

Follow Us

Newsletter

Get coding tips, tutorials, and updates straight to your inbox.