In this article, I will explain classes, objects, access modifiers, constructors, encapsulation, abstraction, inheritance, and polymorphism in C++.
The need for OOP in C++
Hey there, future coders! Ever wondered why everyone keeps talking about OOP when it comes to C++? OOP stands for Object-Oriented Programming. It’s like a set of rules for how to write code. It makes your life easier and your code more organized.
Imagine a dog. A dog can bark, eat, and sleep. In OOP, you’d make a Dog class that holds all these actions (called methods). So, next time you want a dog in your program, you don’t have to write code for barking, eating, and sleeping again. You just say, “Hey, I got a new Dog,” and boom! Your new dog can do all that stuff.
So why do you need to know OOP in C++?
-
Keep your code clean: You don’t want your room messy, right? The same goes for code.
-
Easier to work with others: If everyone follows the same rules, no one’s stepping on each other’s toes.
-
Saves time: Write it once, and use it many times.
Stick around, and you’ll learn the basics of OOP in no time!
Classes and objects
In C++, a class is like a blueprint for creating objects. Objects are instances of classes.
Here’s a simple class definition for a Dog
 class:
class Dog {
public:
std::string name;
int age;
void bark() {
std::cout << this->name << " said: Woof! Woof!" << std::endl;
}
};
Creating an Object
By defining a class, you’re essentially creating a new data type. An object is a variable of this custom data type. You can instantiate an object from the class like so:
int main(void)
{
Dog myDog;
return (0);
}
Accessing Members
You can access the data members and methods using the.
 operator:
int main(void)
{
Dog myDog;
myDog.name = "Rex";
myDog.age = 5;
myDog.bark();
return (0);
}
output:
testing % ./a.out
Rex said: Woof! Woof!
With the Dog
 class, you can create many Dog
 objects, each with its own name
 and age
or any other parameter that you will add to this class.
Access modifiers
Sometimes you need to protect some variables in your class, and for this, we have access modifiers in cpp, there are three types:
Public
: Accessible from anywherePrivate
: Accessible only within the classProtected
: Accessible within the class and its derived classes
Public Access Modifier
Here, a
 is public, so you can access it directly.
class MyClass {
public:
int a;
};
int main(void) {
MyClass obj;
obj.a = 10; /*Allowed*/
return (0);
}
Private Access Modifier
Here, b
 is private, so you can't access it directly. Use a public function to get it.
class MyClass {
private:
int b;
public:
void setB(int val) {
b = val;
}
int getB() {
return (b);
}
};
int main(void) {
MyClass obj;
obj.setB(20);
int b = obj.getB(); /*Allowed*/
obj.b = 3; /*Not allowed, will create error*/
return (0);
}
Protected Access Modifier
Here, c
 is protected, so you can't access it directly. But, you can access it in a derived class.
class Parent {
protected:
int c;
};
class Child : public Parent {
public:
void setC(int val) {
c = val;
}
int getC() {
return (c);
}
};
int main(void) {
Child obj;
obj.setC(30);
int a = obj.getC(); /*Allowed*/
return (0);
}
The main difference between private
 and protected
 is who can access the members:
Private
: Only accessible within the same class.Protected
: Accessible within the same class and also in classes that inherit from it.
Private Example
class Parent {
private:
int a;
};
class Child : public Parent {
public:
void setA(int val) {
/*a = val; Error, can't access private member */
}
};
Protected Example
class Parent {
protected:
int b;
};
class Child : public Parent {
public:
void setB(int val) {
b = val; /*Allowed, b is protected*/
}
};
In the first example, Child
 can't access a
 because it's private in Parent
. In the second example, Child
 can access b
 because it's protected in Parent
.
Constructors
Constructors in C++ are special member functions that get automatically called when an object of a class is created. They usually initialize the object’s attributes.
Syntax
The constructor has the same name as the class and doesn’t return anything.
class MyClass {
public:
MyClass() {
/*Constructor code here*/
}
};
Here’s how you can use a constructor to initialize an object.
#include <iostream>
class Dog {
public:
std::string name;
Dog() {
name = "Unknown";
}
};
int main(void) {
Dog myDog;
std::cout << "Dog name: " << myDog.name << std::endl;
return (0);
}
Output:
testing % ./a.out
Dog name: Unknown
You can also pass parameters to a constructor.
#include <iostream>
class Dog {
public:
std::string name;
Dog(std::string dogName) {
name = dogName;
}
};
int main(void) {
Dog myDog("Buddy");
std::cout << "Dog name: " << myDog.name << std::endl;
return (0);
}
Output:
testing % ./a.out
Dog name: Buddy
Multiple Constructors
You can have more than one constructor, as long as they have different parameters.
class Dog {
public:
std::string name;
int age;
Dog() {
name = "Unknown";
age = 0;
}
Dog(std::string dogName) {
name = dogName;
age = 0;
}
Dog(std::string dogName, int dogAge) {
name = dogName;
age = dogAge;
}
};
Encapsulation
Encapsulation enhances data integrity by locking down data access to specific methods within the class. This secures your code by making variables private and providing controlled ways to modify or retrieve them through setters and getters. It also helps to manage the code more conveniently.
For a better understanding, let’s consider a case with and without encapsulation:
Without Encapsulation
In this scenario, the data is exposed and can be modified directly, leaving it vulnerable to erroneous changes.
class Dog {
public:
int age;
};
int main(void)
{
Dog dog;
dog.age = -15; /*Invalid, but allowed*/
return (0);
}
With Encapsulation
In this case, the data is secured through private access, and can only be changed via specific methods that include validation checks.
class Dog{
private:
int _age;
public:
void setAge(int a){
if (a > 0)
_age = a;
else
_age = 0;
}
int getAge()
{
return (_age);
}
};
int main(void)
{
Dog dog;
dog.setAge(-15); /*Invalid, defaults to zero*/
dog.setAge(15); /*Valid, now _age = 15)*/
return(0);
}
Abstraction
Abstraction in C++ is like using a TV remote. You press the power button to turn the TV on. You don’t need to know how the TV turns on inside; you just press the button. In C++, abstraction lets you hide the complicated stuff and only show the buttons (functions) that are needed.
For a better understanding, let me show a case with and without abstraction:
Without Abstraction
Imagine you have to do 3 steps to start a car: turn a key, push a button, and pump gas.
class Car {
public:
void turnKey() {
/*Turn the key*/
}
void pushButton() {
/*Push the button*/
}
void pumpGas() {
/*Pump gas*/
}
};
With Abstraction
Instead, you can have just one start button (function) that does all those 3 steps for you.
class Car {
private:
void turnKey() {
/*Turn the key*/
}
void pushButton() {
/*Push the button*/
}
void pumpGas() {
/*Pump gas*/
}
public:
void start() {
turnKey();
pushButton();
pumpGas();
}
};
You just need to know the start button, and don’t have to worry about what happens inside. It makes it easier to work with your class.
Inheritance
Inheritance in C++ is similar to passing traits from parents to children. If your parent can run well, you can inherit that ability too.
case with and without inheritance:
Without Inheritance
If you have two types of cars, both can start and stop, but they SportsCar
 can also drift. Without inheritance, you'd write two separate classes.
class Car {
public:
void start() {
/*Start the car*/
}
void stop() {
/*Stop the car*/
}
};
class SportsCar {
public:
void start() {
/*Start the car*/
}
void stop() {
/*Stop the car*/
}
void drift() {
/*Make the car drift*/
}
};
With Inheritance
You can make SportsCar
 a child of Car
. It will automatically get the ability to start and stop from its parent.
class Car {
public:
void start() {
/*Start the car*/
}
void stop() {
/*Stop the car*/
}
};
class SportsCar : public Car {
public:
void drift() {
/*Make the car drift*/
}
};
Polymorphism
Polymorphism is like having a universal remote that can control different devices: TV, fan, etc. You press the “power” button, and each device knows what to do without you having to tell it specifically. And in C++ you can have the same, so to use the same functions, but for different devices.
example of code with and without inheritance:
Without Polymorphism
If you have different types of cars, each with a special way of starting, you’d have to call each one differently.
class Car {
public:
void start() {
/*Start the car*/
}
void makeSound(){
std::cout << "vooom" << std::endl;
}
};
class ElectricCar {
public:
void startElectric() {
/*Start the electric car*/
}
void makeSound(){
std::cout << "vooom" << std::endl;
}
};
in code, it will look like this:
int main(void)
{
Car myCar;
myCar.start();
ElectricCar myElectricCar;
myElectricCar.startElectric();
return (0);
}
With Polymorphism
You can make all car types have a common “start” function, so you can treat them all as just “cars”.
class Car {
public:
virtual void start() {
/*Start the car*/
}
void makeSound(){
std::cout << "vooom" << std::endl;
}
};
class ElectricCar : public Car {
public:
void start() override {
/*Start the electric car*/
}
};
in code, it will look like this:
int main(void)
{
Car* myCar = new Car();
Car* myElectricCar = new ElectricCar();
myCar->start();
myElectricCar->start();
delete(myCar);
delete(myElectricCar);
return (0);
}
With polymorphism, you just have one button or function, and each type of car knows what to do when you press it. This makes it simpler to manage different types of cars.
Why Use Pointers in Polymorphism?
-
Dynamic Behavior: Pointers let your program decide at run-time which version of theÂ
start
 function to use. If you have anÂElectricCar
, it'll useÂElectricCar::start();
 If it's just aÂCar
, it'll useÂCar::start();
. This happens on the fly. -
Flexibility: By using pointers to base classes likeÂ
Car
, you can write code that works for any derived class likeÂElectricCar
 without even knowing what types of cars you might add to the program later. You can easily plug in a new type of car and your existing code will still work. -
Avoid Slicing: Using pointers prevents slicing, which is when you lose the unique behaviors of the derived class (
ElectricCar
) if you try to store it as a base class (Car
). -
Memory Management: Pointers let you control the object’s lifetime. You can create objects when you need them and delete them when you’re done, which can help to save resources.
You mostly lose polymorphism if you don’t use pointers or references. When you use stack-allocated objects, the compiler decides at compile-time which method to call, based on the object’s declared type. This is called “static polymorphism” or “compile-time polymorphism,” and it doesn’t offer the same flexibility as “dynamic polymorphism” or “run-time polymorphism” that you get with pointers or references.
Finish
I hope this article clarifies the intricacies of object-oriented programming in C++ for you. If you have any suggestions or questions, feel free to leave a comment. If you found this guide useful, please consider connecting with me onÂ
Link to follow me:
Also published here.