Understanding Encapsulation in C++

To understand what encapsulation is, I will start off with a brief explanation of setters/getters and class behavior.

Setters/Getters

C++ is an Object-Oriented Programming (OOP) language. One of the principles of OOP is to encapsulate data members as private members and access them through a public access interface.

Let's take for example the class shown below:

class Person{

private:

    int age; //1. Age is a private data member

public:

    Person();

    ~Person();

    void setAge(int uAge); //2. Sets the value of the age member

    int getAge(); //3. Gets the value of the age member

};

The data member 'age' is a private data member (line 1). The value of this data member is accessed either through the method 'setAge' or 'getAge.' The method 'setAge' sets the value of 'age' (line 2). The method 'getAge' retrieves the value of 'age' (line 3).

In Object-Oriented Programming languages, a method that sets the value of a private data member is known as a 'setter.' In contrast, a method that retrieves the value of a data member is known as a 'getter.' Therefore, the 'setAge' method is referred as a setter. Whereas, the 'getAge' method is referred as a getter.

Class Behavior

In a program, a Class represents a real-world object or entity. A class has data members and methods that account for the behavior of the object.

A class behavior is a common action performed by the object. For example, an artist has a set of common behaviors; before painting a masterpiece, an artist prepares the easel, cleans the brushes and starts painting.

A program can represent an artist behaviors or actions through a Class' method. The snippet below illustrates the behaviors of an artist object in C++. See lines 1, 2 & 3.

class Artist{

private:

public:

    Artist();
    ~Artist();

    void prepareEasel(); //1. Prepares the easel

    void cleanBrushes(); //2. Cleans the brushes

    void paint(); //3. Artist's paint style  
};

Encapsulation

Encapsulation is the act of making data members private and accessing them using a public interface; through setters and getters methods.

Object-Oriented Programming principles suggest always to encapsulate data members. However, data members are not the only entities that should be encapsulated. A Class behavior should also be encapsulated; in particular, behaviors of a class that varies.

Let's analyze the Artist class shown above. The class contains these actions:

  • Prepares Easel
  • Cleans Brushes
  • Paints

Of these behaviors, painting differs among painters. For example, an artist's could paint in a modern, impressionist or abstract style. The other two actions do not change among painters. Every artist prepares an easel and cleans a brush the same way.

Therefore, we should encapsulate the 'paint' behavior since it varies from object to object.

In C++, abstract classes are used to encapsulate behaviors. The snippet below shows an abstract class, 'PaintStyle,' that encapsulates the 'paint' behavior (see line 1).

class PaintStyle{

public:

    virtual void paint()=0;  //1. Paint virtual method

    virtual ~PaintStyle(){}; //2. Virtual destructor
};

To make use of the abstract class, you need to create a subclass. The snippet below shows a subclass of 'PaintStyle.' Notice that its 'paint' behavior prints "I paint in a modern style" (line 4).

class ModernPaintStyle:public PaintStyle{

private:

public:

    ModernPaintStyle(); //1. Constructor

    ~ModernPaintStyle(); //2. Destructor

    void paint(); //3. Paint method

};

ModernPaintStyle::ModernPaintStyle(){}

ModernPaintStyle::~ModernPaintStyle(){}

void ModernPaintStyle::paint(){

    std::cout<<"I paint in a modern style"<<std::endl; //4. Prints "I paint in modern style"

}

Let's create a second subclass of 'PaintStyle.' But this time set the behavior to print "I paint in an impressionist style" (line 4).

class ImpressionistPaintStyle:public PaintStyle{

private:

public:

    ImpressionistPaintStyle(); //1. Constructor

    ~ImpressionistPaintStyle(); //2. Destructor

    void paint(); //3. Paint method

};

ImpressionistPaintStyle::ImpressionistPaintStyle(){}

ImpressionistPaintStyle::~ImpressionistPaintStyle(){}

void ImpressionistPaintStyle::paint(){

    std::cout<<"I paint in an Impressionist style"<<std::endl; //4. Prints "I paint in an Impressionist style"

}

Let's go back to the Artist class and apply the changes shown below.

The snippet shows a pointer member of type 'PaintStyle' (line 1). Line 4 illustrates the addition of the method 'setPaintStyle.' This method along with the modification of the 'paint' method (line 5) injects polymorphism into the class. That is, the Artist class can change behaviors during runtime (See lines 6 &7).

class Artist{

private:

    PaintStyle *artistStyle; //1. Pointer member to 'PaintStyle'

public:

    Artist();
    ~Artist();

    void prepareEasel(); //2. Prepares the easel

    void cleanBrushes(); //3. Cleans the brushes

    void setPaintStyle(PaintStyle *uStyle); //4. Sets the paint style

    void paint(); //5. Artist's paint style

};

Artist::Artist(){}

Artist::~Artist(){}

void Artist::setPaintStyle(PaintStyle *uStyle){

    artistStyle=uStyle; //6. Sets the style of the painter
}

void Artist::paint(){

    artistStyle->paint(); //7. Calls the paint method of the subclass

}

Polymorphism permits the artist class to paint in a modern style. The next minute it can behave like an artist that paints in an impressionist style.

The snippet below showcase these properties.

int main(){

    Artist *picasso=new Artist(); //1. Create an instance of an artist

    PaintStyle *modernStyle=new ModernPaintStyle(); //2. Create an instance of a modern painter style

    PaintStyle *impressionistStyle=new ImpressionistPaintStyle(); //3. Create an instance of an impressionist painter style

    picasso->setPaintStyle(modernStyle); //4. sets the painter style to a modern style

    picasso->paint(); //5. Calls paint method. It prints "I paint in a modern style"

    picasso->setPaintStyle(impressionistStyle); //6. Change the painter style to an impressionist style

    picasso->paint(); //7. Calls the paint method. It prints "I paint in an impressionist style"

    return 0;

}

Line 1 creates an instance of an artist object. Lines 2 & 3 creates instances of different paint styles. The painter style is set in line 4, and when the method 'paint' is executed, it prints "I paint in a modern style" (line 5).

Because of polymorphism and encapsulation, the behavior of the class can change with a simple method call as shown in line 6. When the 'paint' method is called again, it prints "I paint in an impressionist style" (line 7).

Polymorphism and encapsulation give flexibility and modularity to an application. And it enables a program to be extended and modified with few changes. For example, years from now, a new paint style can be added without ever touching the Artist class.

Hope this helps.

Understanding Polymorphism in C++

Polymorphism gives flexibility to a program. To understand how polymorphism provides flexibility to your program, let's start off with a brief introduction to Abstract Classes.

An abstract class is a class that acts as a legal contract. The legal contract states that if you are a subclass of an abstract class, you must implement every method of the abstract class.

An abstract class has the following properties:

  1. It does not have a constructor method.
  2. Its destructor must be declared as a virtual method.
  3. Since it does not have a constructor, you can't create instances of an abstract class.
  4. Methods in an abstract class must contain the keyword 'virtual' in their declaration
  5. Methods in a virtual class are always set to '0.'

The snippet below declares an abstract class called "Dog."

class Dog{

private:

public:

    virtual void run()=0; //1. Virtual Run method

    virtual ~Dog(){}; //2. Virtual destructor

};

Line 1 declares the virtual 'run' method. Line 2 declares the class virtual destructor. Notice that these methods are declared within the public interface. A different name for an abstract class is an Interface.

Since an instance of an abstract class is not possible, an abstract class requires subclasses to be useful. As stated earlier, the subclass must implement every method of the abstract class.

The snippet below declares the subclass 'Beagle.' The implementation of the 'run' method is shown in line 5.

class Beagle:public Dog{

private:

public:
    Beagle(); //1. Constructor

    ~Beagle(); //2. Destructor

    void run(); //3. Implement the run method

};

Beagle::Beagle(){}

Beagle::~Beagle(){}

void Beagle::run(){

    std::cout<<"I run like a beagle"<<std::endl; //5. Print "I run like a beagle"

}

In practice, you would create an instance of the 'Beagle' subclass as shown in the snippet below:

int main(){

    Beagle *myBeagle=new Beagle(); //1. Creating an instance of the subclass

    return 0;
}

However, you shouldn't create the instance as shown above. Instead, you should create the instance as shown below:

int main(){

    Dog *myBeagle=new Beagle(); //1. Code to the interface, not the implementation

    return 0;
}

Notice that myBeagle points to the abstract class type. It does not point to the subclass type. This approach is known as "Code to the interface, not to the implementation" and is one of the 7 Principles of Object-Oriented Programming. In the statement above, "Interface" refers to an Abstract Class. "Implementation" refers to a Subclass.

"Code to the interface, not to the implementation" injects polymorphism into your project, thus making it flexible.

To see polymorphism in action, let's create a subclass call 'Pitbull' as shown below:

class Pitbull:public Dog{

private:

public:
    Pitbull(); //1. Constructor

    ~Pitbull(); //2. Destructor

    void run(); //3. Implement the run method

};

Pitbull::Pitbull(){}

Pitbull::~Pitbull(){}

void Pitbull::run(){

    std::cout<<"I run like a pitbull"<<std::endl; //5. Print "I run like a pitbull"

}

As required, the subclass implemented the 'run' method (line 5).

Let's create a function called 'youRunLike' as shown below. Notice the argument of the function is of type 'Dog,' the abstract class.

void getRunSpeed(Dog *uDog); //1. Function declaration

void getRunSpeed(Dog *uDog){ //2. Function definition

    uDog->run(); //3. Call the run method

}

Notice the call to the 'run' method of the 'Dog' class (line 3). This is a bit weird. The abstract class never implemented this method, only its subclasses have. How does it know which subclass to call?

Polymorphism allows the function to use the same argument for either subclass type, 'Beagle' or 'Pitbull' and it will direct the call to the correct 'run' method. You do not need to create different functions with different argument types.

For example, in the snippet below, lines 1 & 2 creates the instances of the subclasses using the "Code to the interface, not to the implementation" approach. Lines 3 & 4 calls the function 'youRunLike.'

int main(){

    Dog *myBeagle=new Beagle(); //1. Instance of Beagle

    Dog *myPitbull=new Pitbull; //2. Instance of Pitbull

    getRunSpeed(myBeagle); //3. Call the function-It prints "I run like a beagle"

    getRunSpeed(myPitbull); //4. Call the function- It prints "I run like a pitbull"

    return 0;
}

When you execute the snippet above, line 3 will print 'I run like a beagle.' Whereas, line 4 will print 'I run like a Pitbull.' As expected, the correct 'run' method for each subclass was evoked.

Polymorphism gives flexibility and modularity to an application. If 100 years from now, a new subclass is defined, it will be able to use the same 'youRunLike' function without any modifications.

Hope this helps

Understanding Inheritance in C++

Inheritance is when one class inherits the behaviors of a parent class. That is, a class can inherit the data members and methods of a parent class.

For example, the snippet below shows a class of type Dog with two methods: bark and run.

class Dog{

private:

public:
    Dog();    //1. Constructor
    ~Dog();  //2. Desctuctor

    void bark(); //3. Bark method
    void run(); //4. Run method

};

Dog::Dog(){

    std::cout<<"Dog creat"<<std::endl;
}

Dog::~Dog(){}

void Dog::bark(){

    std::cout<<"I bark"<<std::endl; //5. Print "I bark"
}

void Dog::run(){

    std::cout<<"I run"<<std::endl; //6. Print "I run"

}

These methods have a particular behavior. The bark method prints "I bark" (line 5). The run method prints "I run" (line 6).

Just like a child inherits the traits of his father, a child class can inherit the traits of a parent class. In C++, a subclass refers to a child class. A super class refers to a parent class.

Inheritance in C++ requires the following syntax:

class subclass_type::public superclass_type{};

For instance, the snippet below declares the Beagle class a subclass of the Dog class:

class Beagle:public Dog{

private:

public:
    Beagle(); //1. Constructor
    ~Beagle(); //2. Destructor

};

Beagle::Beagle(){}

Beagle::~Beagle(){}

Notice a crucial point. The Beagle class does not need to declare nor define the bark and run methods. However, it can use them.

For example, the snippet below creates an instance of a Beagle class and access the bark and run methods. See lines 2 & 3.

int main(){

    Beagle beagle; //1. Create an instance of the class

    beagle.bark(); //2. Access the bark method
    beagle.run(); //3. Access the run method

    return 0;
}

When you execute the snippet above, the program will print "I bark" and "I run."

Overwriting a Method

Not every dog barks nor runs the same. These behaviors among dogs are different. This fact implies that we need to modify the behaviors of the Beagle class. C++ allows a subclass to change its behaviors by overwriting a method.

To overwrite a method, you need to declare and define the method you want to modify. In the example below, the bark and run methods have been modified to print: "I bark like a Beagle" and "I run like a Beagle." See lines 5 and 6.

class Beagle:public Dog{

private:

public:
    Beagle(); //1. Constructor
    ~Beagle(); //2. Destructor

    void bark(); //3. Overwrite the bark method

    void run(); //4. Overwrite the run method

};

Beagle::Beagle(){

    std::cout<<"Beagle creat"<<std::endl;

}

Beagle::~Beagle(){}

void Beagle::bark(){

    std::cout<<"I bark like a beagle"<<std::endl; //5. Print "I bark like a beagle"

}

void Beagle::run(){

    std::cout<<"I run like a beagle"<<std::endl; //6. Print "I run like a beagle"

}

When you execute the snippet below, the bark and run methods will print: "I bark like a beagle" and "I run like a beagle."

int main(){

    Beagle beagle; //1. Create an instance of the class

    beagle.bark(); //2. Access the bark method- It prints "I bark like a beagle"
    beagle.run(); //3. Access the run method- It print "I run like a beagle"

    return 0;
}

Before I conclude this lesson, I want to state a crucial point of inheritance in C++. When you create an instance of a class, C++ calls the class constructor. However, when you create an instance of a subclass, C++ calls the superclass constructor and the subclass constructor. C++ calls the superclass constructor before calling the subclass constructor.

Hope this helps

Dynamic Memory Allocation in C++

In a video game, when a new character shows up in a scene, memory is allocated for the character. The game developers made sure the game allocates enough memory for the character before you interacted with it. This allocation happens dynamically during runtime, and it is known as Dynamic Memory Allocation.

C++ provides two methods for allocating and deallocating memory: Low-level memory management and an object-oriented memory management. Low-level memory management uses the malloc and free functions to allocate and deallocate memory. Object-oriented memory management uses the new and delete operator instead.

Let's understand how malloc and free works before we learn about the new and delete operator.

Low-Level Memory Allocation

malloc function

The function malloc returns the address of a memory location of a particular size. The syntax is as follows:

my_pointer=(typecast)malloc(size_of_memory);

For example, let's say you need a chunk of memory one integer size long. To request this chunk of memory, you call the malloc function as follows (line 1):

int main(){

    int *p=(int*)malloc(sizeof(int)); //1. Allocating memory

    return 0;
}

Couple of things to note in the example above:

  1. "p" is defined as a pointer. Why? Because only Pointers can receive addresses. The malloc function returns an address location.
  1. malloc returns an address, but it does not know the data type the address will represent. Since the pointer "p" is a pointer to an "int" data type, you must typecast the address with "(int *)".

  2. The argument of malloc represents the memory size you want to allocate. Typically, you use the function "sizeOf()" in the malloc argument to represent a size. In the example above, malloc allocated the representation of an "int" data type.

After memory allocation, you can use the pointer as usual. For example, in the snippet below, the pointer is dereferenced and given the value of five (line 2).

int main(){

    int *p=(int*)malloc(sizeof(int)); //1. Allocating memory

    *p=5; //2. Assigning the value of 5 to p

    return 0;
}

free function

The free function deallocates (gives back) previously allocated memory back to the system. You must always deallocate all previously allocated memory. If you do not, it will lead to memory leaks in your application.

The free function syntax is as follows:

free(my_pointer);

The snippet below deallocates the section of memory allocated in our previous example:

[example 3]

int main(){

    int *p=(int*)malloc(sizeof(int)); //1. Allocating memory

    *p=5; //2. Assigning the value of 5 to p

    free(p); //3. deallocate the memory

    return 0;
}

Object-Oriented Memory Allocation

The functions malloc and free are often used in the C Programming Language. In C++, the operators new and delete are used instead. The new and delete operators perform the same operation as malloc and free, respectively. However, the new and delete operators are often used to allocate memory for user-defined types such as classes.

new Operator

The new operator does the following operations:

  1. It allocates memory for a User Defined Type object.
  2. It initializes the object. i.e., it calls the class constructor. The initialization ensures that the object is properly initialized before use.
  3. It returns an address to the object allocated. As opposed to malloc, you do not need to typecast the return value.

The syntax for the new operator is as follows:

my_pointer=new User_Defined_Type();

The snippet below shows the allocation of a class object using the "new" operator (line 3)

[example 4]

class Student{

private:

public:
    Student(){}; //1. Constructor

    ~Student(){}; //2. Destructor

};

int main(){

    Student *p=new Student(); //3. Allocating memory for the class

    return 0;
}

delete Operator

The delete operator behaves exactly like the free function with a minor difference. It calls the class destructor before deallocating the object.

The syntax of the delete operator is as follows:

delete my_pointer;

The snippet below shows the deallocation of the class object using the "delete" operator (line 4)

[example 5]

class Student{

 private:

 public:
     Student(){}; //1. Constructor

     ~Student(){}; //2. Destructor

 };


 int main(){

     Student *p=new Student(); //3. Allocating memory for the class

     delete p; //4. Deallocating class object memory

     return 0;

 }

Remember this crucial point, the new operator calls the constructor of the class, whereas the delete operator calls the destructor of the class.

Hope this helps

User-Defined Types in C++: Part II

The second mechanism used to create User-Defined Types is a class. A class is made up of data and function members. A function that belongs to a class is called a Method.

Syntax

A Class consists of the following syntax:

class User_Defined_Type_Name{

 private:

 public:

 };

The class keyword declares a class of type "User_Defined_Type_Name" and defines the data members within the class.

A class has a Constructor and Destructor method. The constructor method initializes the class to a safe state. The destructor cleans up after the class object is no longer needed. Furthermore, the constructor and destructor have the same name as the class.

For example, let's define a Student class with a Constructor and Destructor methods.

class Student{

 private:

 public:
    Student();  //1. Constructor

    ~Student(); //2. Destructor

};

Line 1 shows the constructor of the class. Line 2 illustrates the destructor of the class. Notice the tilde symbol "~" prefixed to the destructor's name.

In practice, you create a class object as shown in the snippet below (line 3). Upon creation of the object, the constructor method is called. When the class object is destroyed, the destructor method is called.

int main(){

    Student harold; //3. Create a class object

    return 0;
}

Access Specifiers

As mentioned, a class contains data members and methods. These members can be accessed through the dot operator "." However, their access is controlled by the class Access Specifier.

There are three kinds of Access Specifier: Public, Private or Protected. Members declared within the Private Access Specifier are not accessible outside the class. Whereas, members declared within the Public Access Specifier are accessible outside the class.

For example, line 1 shows the data member "age" defined within the private access specifier.

class Student{

 private:

    int age; //1. define data member

 public:
     Student();  //2. Constructor

     ~Student(); //3. Destructor

};

If you try to access this data member as shown below, you will get an error (line 2). Data members can only be accessed outside the class if it is defined within the public access specifier.

int main(){

    Student harold; //1. Create a class object

    harold.age=29; //2. ERROR. Age is a private data member
    return 0;
}

So, why not declare all members as Public?

C++ is an Object-Oriented Programming (OOP) language. One of the principles of OOP is to encapsulate data members as private and access them through the public access interface.

Instead of making "age" public, we are going to keep it as private but access it through a method declared in the public access specifier, also know as the Public Interface.

For example, in the snippet below, the "setAge" method is declared in the Public Interface (line 4). And will be responsible for setting the value of the data member "age."

class Student{

private:

    int age; //1. define data member

public:
    Student();  //2. Constructor

    ~Student(); //3. Destructor

    void setAge(int uAge); //4. Method to access the private "age" member
};

Now, let's define the "setAge()" method.

Defining Methods in C++

In C++, a method is defined with the following syntax:

return_type class_name::method_name(arguments){}

For example, the setAge() method is defined as follows

void Student::setAge(int uAge){}

The Constructor and Destructor methods are defined similarly but without a return type.

Student::Student(){} //Constructor
Student::~Student(){} //Destructor

The code snippet below shows the definition of the constructor, destructor and "setAge" methods (see lines 6,7 & 8).

Notice an additional method "getAge" defined in line 9. This method retrieves the value of the data member.

class Student{

private:

    int age; //1. define data member

public:
    Student();  //2. Constructor

    ~Student(); //3. Destructor

    void setAge(int uAge); //4. Method to set the value of private "age" member

    int getAge(); //5. Method to retrieve the value of private "age" member
};

Student::Student(){

    std::cout<<"Creating Class"<<std::endl; //6. Print "Creating Class"
}

Student::~Student(){

    std::cout<<"Removing Class"<<std::endl; //7. Print "Removing Class"
}

void Student::setAge(int uAge){

    age=uAge; //8. Set "age" value
}

int Student::getAge(){

    return age; //9. Return "age" value

}

Initializing Data Members

Data members must be initialized in the constructor using an Initialization List. The syntax is as follows:

class_name::constructor():data_member1(value1),data_member2(value2){}

In the example below, the "age" data member is initialized to zero. See line 1.

Student::Student():age(0){ //1. Initialization "age" to zero.

}

Using the Class

The final version of the Student Class is as follows:

class Student{

private:

    int age; //1. define data member

public:
    Student();  //2. Constructor

    ~Student(); //3. Destructor

    void setAge(int uAge); //4. Method to set the value of private "age" member

    int getAge(); //5. Method to retrieve the value of private "age" member
};

Student::Student():age(0.0){

    std::cout<<"Creating Class"<<std::endl; //6. Print "Creating Class"
}

Student::~Student(){

    std::cout<<"Removing Class"<<std::endl; //7. Print "Removing Class"
}

void Student::setAge(int uAge){

    age=uAge; //8. Set "age" value
}

int Student::getAge(){

    return age; //9. Return "age" value

}

In practice, you create a class object as shown in the snippet below (line 1).

Line 2 shows how to set the value of "age" using the "setAge" method. Line 3 shows how to retrieve the value of "age" using the "getAge" method. Lastly, the age is printed in line 4.

int main(){

    Student harold; //1. Create a class object

    harold.setAge(29); //2. set age to 29

    int studentAge=harold.getAge(); //3. get the current age

    std::cout<<"Age: "<<studentAge<<std::endl; //4. print the age

    return 0;
}

Now that you know about classes, it's time to learn about the new and delete operators utilized in the creation and destruction of a class.

Hope this helps.