paint-brush
Why Object-Oriented Programming in C++ Mattersby@zhadan
131 reads

Why Object-Oriented Programming in C++ Matters

by Anatolii ZhadanOctober 18th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this article, I will explain classes, objects, access modifiers, constructors, encapsulation, abstraction, inheritance, and polymorphism in C++.

People Mentioned

Mention Thumbnail
featured image - Why Object-Oriented Programming in C++ Matters
Anatolii Zhadan HackerNoon profile picture

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++?

  1. Keep your code clean: You don’t want your room messy, right? The same goes for code.

  2. Easier to work with others: If everyone follows the same rules, no one’s stepping on each other’s toes.

  3. 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 ageor 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:

  1. Public: Accessible from anywhere
  2. Private: Accessible only within the class
  3. Protected: 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?

  1. 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.

  2. 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.

  3. 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).

  4. 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 LinkedIn and leaving feedback.

LinkedIn

Hackernoon


Also published here.