Object-Oriented Programming (C++)-The Easy Way (Part-II)

Object-Oriented Programming (C++)-The Easy Way (Part-II)

What’s up fellas? This what you see is the second part of my Object-Oriented Programming concepts series. I would be covering some interesting stuff including Copy Constructors, Friend functions, Structs and many more.

- Did you know that we could copy our constructors?

If we go by the literal definition, a Copy Constructor simply means an overloaded constructor used to declare and initialize an object of a particular class from another object of the same class. Puzzled? Let’s try to clear out the confusion in the room with the help of a code:

#include<iostream>
using namespace std;

class Country{
    public:
        string name;
        int populationCount;

    //Parameterized Counstructor
    Country(string s, int n){
        this->name = s; // or name = s
        populationCount = n; // or this->populationCount = n
    }

    void printCountryDetails(){
        cout<<"Country Name: "<<this->name<<endl;
        cout<<"Population Count: "<<this->populationCount<<endl;
    }
};

int main(){
    Country c1 = Country("India", 1300000000);
    Country c2 = c1;
    c2.printCountryDetails();
}
/*
Output:
Country Name: India
Population Count: 1300000000
*/

In the above program, we created a copy of the object c1 using the assignment operator (=) and called its member function, printCountryDetails() to display the member fields. Here, the assignment operator calls the Default Copy Constructor given by the compiler which shallow copies all the member fields. Wait, shallow copy?

Well, a shallow copy occurs when the data of all the member fields of a class instance is copied as it is to another. Simple? But, where would a pointer point if it were also a class member? Would it also have two copies? What would happen if I update/delete one of the pointer’s data?

Well, this is a drawback of the shallow copy, it doesn’t dynamically allocate a separate memory location for the copied pointer. This simply means, when the copy is done, there would be two pointers pointing to the same memory location and any update to it will also affect the other pointer.

1_-F52He4R-0NhEEkFmUPhtA.png

So how do we deal with this? Creating a User-defined Copy Constructor can help! Let’s digest it with the help of a code:

#include<iostream>
using namespace std;

class Country{
    public:
        string name;
        int populationCount;
        int *stateCount;

    Country(){
        stateCount = new int; //dynamically allocate memory for an integer and return it's address
    }

    //User defined copy constructor
    Country(Country &c){
        name = c.name;
        populationCount = c.populationCount;
        stateCount = new int; //dynamically allocate memory for an integer and return it's address
        *stateCount = *(c.stateCount);
    }

    void setCountryDetails(string s, int n, int count){
        this->name = s; // or name = s
        populationCount = n; // or this->populationCount = n
        *stateCount = count;
    }

    void printCountryDetails(){
        cout<<"Country Name: "<<this->name<<endl;
        cout<<"Population Count: "<<this->populationCount<<endl;
        cout<<"State Count: "<<*stateCount<<endl;
    }
};

int main(){
    Country c1;
    c1.setCountryDetails("India", 1300000000, 28);
    Country c2 = c1;
    int num = 29;
    c2.stateCount = &num;
    cout<<"C2"<<endl;
    c2.printCountryDetails();
    cout<<"C1"<<endl;
    c1.printCountryDetails();
}
/*
Output:
C2
Country Name: India
Population Count: 1300000000
State Count: 29
C1
Country Name: India
Population Count: 1300000000
State Count: 28
*/

Above, we used our own copy constructor which dynamically allocates different memory locations for the class members and copies data onto them.

This behavior is also called Deep Copy, which is possible when we write our own copy constructor.

As we now are, a little bit versed with the concept of copy constructors, moving forward, I had a question in my mind!

Did we know we don't always need to create an instance object in order to access the methods and fields within a class?

Yes! We can simply call a particular method or a field by making it Static. Static is a keyword that doesn’t belong to any particular class or its instance.

Let’s understand the use of static with the help of an example:

#include<iostream>
using namespace std;

class Country{
    public:
        string name;
        int populationCount;
        static int countryCount;

    void setCountryDetails(string s, int n){
        this->name = s; // or name = s
        this->populationCount = n;
        countryCount++;
    }

    static int getTotalCountryCount(){
        return countryCount;
    }
};

int Country::countryCount = 0;

int main(){
    Country c1;
    c1.setCountryDetails("India", 1300000000);
    Country c2;
    c2.setCountryDetails("USA", 328200000);
    cout<<"Total Countries: "<<Country::getTotalCountryCount()<<endl;
}
/*
Output:
Total Countries: 2
*/

A static member function can only access static data member, other static member functions and any other functions from outside the class. Moreover, it can only be accessed with the class name and scope resolution (::) operator as we could see in the above code.

Static member functions have a class scope and they do not have access to this pointer of the class. If at all you intend to do so, don't be surprised when the below error pops!

“ ‘this’ may only be used inside a nonstatic member function.”

Using the static keyword also has an advantage! We don't need to create a class instance now to access the static members of the class. Also, the static member will not be assigned a separate memory next time when a new class instance is created. What a memory-efficient move!

Since we are so much into classes, how about we make a stride back, investigate Structs and perceive how well it does its job compared to a class!

- C++ Structs

Generally, structs are used for storing light-weight objects. Some people often use struct when they want it to contain only data members and not member functions, although it supports both! But, people generally have a feeling of the struct as a plain-old-data (POD) type and don’t often write functions within it!

Let’s try to grasp this concept with the help of a code:

#include<iostream>
using namespace std;

struct Country{
    string name;
    int populationCount;

    Country(string s, int n){
        name = s;
        populationCount = n;
    }

    void showCountryDetails(){
        cout<<"Country Name: "<<name<<endl;
        cout<<"Country Population: "<<populationCount<<endl;
    }
};

int main(){
    struct Country c1 = Country("India", 1300000000);
    c1.showCountryDetails();
    return 0;
}
/*
Output:
Country Name: India
Country Population: 1300000000
*/

Above, I tried to create the exact same scenario as we had in our class example. Both have a constructor method and other member functions as well. Some more examples might include storing the dimensions of a polygon within a struct and then using it to calculate its area.

Till now, we haven’t really compared structs with classes? So, how do they differ?

Well, a major difference between them is, by default the members of a class are declared with a private access modifier whereas, in a struct, the default access modifier is public. Similarly, when inherited, if not specified explicitly, a struct will inherit publicly whereas, a class will inherit privately.

That’s all for structs, I guess! You can definitely experiment with different use cases of a struct going forward!

Since we are discussing data structs, let’s not leave our enum behind!

- C++ Enumeration (enum)

We use enum when we want a variable to have a value from a specific set of predefined values. This can be helpful in switch statements where we don’t want the switch variable to have any other value than our defined cases. Well, obviously we can rely on the Default for such cases.

For e.g. a variable Direction can have only four concrete values as {“North”, “South”, “East”, “West”} and any other value should throw an error! If you feel the direction could also be, “North-West”? Feel free to add it to your custom enum! ;-)

Let’s grasp this concept quickly through a small piece of code:

#include<iostream>
using namespace std;

enum Direction {North, South, East, West} dir;
enum Gender {Male, Female};

int main(){

    //Example 1
    dir = East;
    cout<<"Currenct Direction: "<<dir<<endl;
    //dir = NorthEast; //Error: identifier "NorthEast" is undefined
    for(int i= North; i<= West; i++){
        cout<<i<<" ";
    }
    cout<<endl;

    //Example 2
    Gender g = Male;
    cout<<"Gender: "<<g<<endl;
    switch (g)
    {
        case Male:
            cout<<"Male it is!"<<endl;
            break;
        case Female:
            cout<<"Female it is!"<<endl;
            break;
    }


    return 0;
}
/*
Output:
Currenct Direction: 2
0 1 2 3 
Gender: 0
Male it is!
*/

To add, enum variables are static and final implicitly. I didn’t tell you final right? Well, an enum class can’t be inherited and hence is final. The final specifier in C++ when added to a function, prevents it from being overridden by a base class.

Don’t forget to notice that I used two separate ways to declare the enum variable. Furthermore, enum variables also iterable! Isn’t it great?

Since you have read (I assume!) the blog till here, we are friends now! :-P

- Friend Functions

Like real-life friends, C++ functions/classes being a friend to another class can access its private/protected members. We just need to declare the function as a friend function within the scope of the class. The function definition itself is not in the class scope and hence, can not be called using a class object.

Let’s clear the confusion through an example:

#include<iostream>
using namespace std;

class Country{
    private: 
        string name;
        int populationCount;

    public: 
        Country(string s, int n){
            name = s;
            populationCount = n;
        }

        friend void getPopulationCount(Country);
};

void getPopulationCount(Country c){
    cout<<"Population Count: "<<c.populationCount;
}

int main(){
    Country c1 = Country("India", 130000000);
    getPopulationCount(c1);
    return 0;
}
/*
Output:
Population Count: 130000000
*/

Here, the function getPopulationCount() is declared as a friend function and its definition lies outside the scope of the class Country. Another point to note is that a friend function can be invoked like a normal function without using the object. It cannot access the class member names directly and has to use an object name and dot membership operator (.) with the member name.

It’s not like only a function can be a friend to a particular class. But, a class can also be a friend to another.

#include<iostream>
using namespace std;


class Country{
    private: 
        string name;
        int populationCount;

    public: 
        Country(string s, int n){
            name = s;
            populationCount = n;
        }
        friend class Continent;       
};

class Continent{
    private:
        string name;
        int totalCountries;

    public:
        Continent(string s, int n){
            name = s;
            totalCountries = n;
        }
        void getCountryPopulationCount(Country &c);   
};


void Continent::getCountryPopulationCount(Country &c){
    cout<<"Country Name: "<<c.name<<endl;
    cout<<"Population Count: "<<c.populationCount<<endl;
}

int main(){
    Country c1 = Country("India", 130000000);
    Continent n1 = Continent("Asia", 48);
    n1.getCountryPopulationCount(c1);
    return 0;
}
/*
Output:
Country Name: India
Population Count: 130000000
*/

That’s it for this blog, I guess. We were able to cover copy constructors, static, structs, enums, and friend functions in this blog.

About me? I am a tech lover! I love to read, implement and explain :)

Best.