The blog post introduces the concept of classes in C++.

Class
A class is like a template or a skelton that defines the attributes and methods of an object. The following shows how we can define a class and initialize instances of objects.
class Dog {
public:
// public attributes
string name;
int age;
// public methods
void barks () {
cout << "Woof Woof!" << endl;
}
};
int main () {
Dog dog1;
dog1.name = "Bell";
dog2.age = 9;
cout << "Dog's name is " << dog1.name << ", and it is " << dog1.age << " years old." << endl;
// => Dog's name is Bell, and it is 9 years old.
dog1.barks();
// => Woof Woof!
return 0;
}
The public
keyword means that the attributes and methods are accessible from the outside. (will be
covered in defail in the next article. )
Although the class may seem like a small feature, it has created a new programming paradigm,
and we will explore its impact going forward.
Constructor & Destructor
When declaring and initializing an object with a class, we often want to set default values. We can achieve this by setting up a default constructor, as shown below.
class Dog {
public:
// public attributes
string name;
int age;
// public methods
void barks () {
cout << "Woof Woof!" << endl;
}
Dog () {
name = "Unnamed";
age = 0;
}
};
int main () {
Dog dog;
cout << "Dog's name: " << dog.name << ", Dog's age: " << dog.age << endl;
// => Dog's name: Unnamed, Dog's age: 0
return 0;
}
The default constructor sets the values of the attributes to default. You can also set up a parameterized constructor, which takes parameters and initializes based on them.
class Dog {
public:
// public attributes
string name;
int age;
// public methods
void barks () {
cout << "Woof Woof!" << endl;
}
Dog () {
name = "Unnamed";
age = 0;
}
Dog (string n, int a) {
name = n;
age = a;
}
};
int main () {
Dog dog;
cout << "Dog's name: " << dog.name << ", Dog's age: " << dog.age << endl;
// => Dog's name: Unnamed, Dog's age: 0
Dog dog1("Bell", 9);
cout << "Dog's name: " << dog1.name << ", Dog's age: " << dog1.age << endl;
// => Dog's name: Bell, Dog's age: 9
return 0;
}
You can create multiple constructors with different parameters, and the compiler will identify the appropriate one. The constructor is also useful when dynamically allocating memory to an object’s attributes.
class ArrayList {
public:
// public attributes
int size;
int *array;
ArrayList (int s) {
size = s;
*array = new int[s];
}
};
However, you need to free the memory to prevent memory leaks. You can do that with a destructor, which runs when the object is no longer in use. You can define a destructor as follows:
class ArrayList {
public:
// public attributes
int size;
int *array;
ArrayList (int s) {
size = s;
*array = new int[s];
}
~ArrayList () {
delete array;
}
};
Member Initializer Lists
If you look closely at the example of the Dog
class, you'll notice that the parameter names
are set to n
and a
instead of name
and age
to prevent ambiguity with the class attributes.
However, it would be clearer if we could use the same names. To do so, we can use member
initializer lists, as shown below.
class Dog {
public:
// public attributes
string name;
int age;
// public methods
void barks () {
cout << "Woof Woof!" << endl;
}
Dog (string name, int age) :
name(name),
age(age) {}
};
When defining attributes, the member initialization functions are automatically created,
allowing you to initialize attributes with the same names as the inputs. Member initializer
lists are also useful when initializing const
attributes, object attributes without a default
constructor, or reference attributes.
this
Keyword
The this
keyword is a pointer to the object being created. It provides another way to use the
same names for parameters and attributes.
class Dog {
public:
// public attributes
string name;
int age;
// public methods
void barks () {
cout << "Woof Woof!" << endl;
}
Dog (string name, int age) {
this->name = name;
this->age = age;
}
};
It can also be useful for creating method chaining, which looks like the following.
class Dog {
public:
// public attributes
string name;
int age;
// public methods
Dog& barks () {
cout << "Woof Woof!" << endl;
return *this; // dereference this pointer and return as reference
}
Dog& sits () {
cout << "Sit Down." << endl;
return *this;
}
};
int main () {
Dog dog;
dog.barks().sits(); // Method Chaining
// => (dog.barks() => dog).sits()
return 0;
}
Constructor Delegation & Copy Constructor
When a class has multiple constructors with different numbers of parameters, you can use constructor delegation to shorten the code. (Depending on the C++ version.)
class Dog {
public:
// public attributes
string name;
int age;
Dog (string name) {
this -> name = name;
}
Dog (string name, int age) : Dog (name) {
this -> age = age;
}
};
You can also pass an object to a constructor to create a copy, as shown below.
class Dog {
public:
// public attributes
string name;
int age;
Dog (string name) {
this -> name = name;
}
Dog (string name, int age) : Dog (name) {
this -> age = age;
}
Dog (const Dog& dog) {
this -> name = dog.name;
this -> age = dog.age;
}
};
int main () {
Dog dog("Bell", 9);
Dog dogCpy = dog;
cout << "Copied name: " << dogCpy.name << endl;
// => Copied name: Bell
return 0;
}
The copy constructor above is the default copy constructor that is made available by default. The reason for introducing it here is that you need to be careful when copying objects that have pointers as attributes.
class ArrayList {
public:
int* array;
int size;
ArrayList (int size) {
this -> size = size;
array = (int *) malloc(size * sizeof(int));
}
~ArrayList () {
free(array);
}
};
int main () {
ArrayList list(5);
ArrayList copy = list;
cout << "Original Address: " << list.array << ", Copied Address: " << copy.array << endl;
// => same address!
exit(0);
return 0;
}
When you run the above code, you'll observe that the addresses of the pointers in list
and copy
are the same. This is problematic because changes to list.array
will also affect copy.array
.
Additionally, if you remove exit
from the code, the program will crash because the same memory
is freed twice. To avoid this, we use a deep copy constructor like the one shown below.
class ArrayList {
public:
int* array;
int size;
ArrayList (int size) {
this -> size = size;
array = (int *) malloc(size * sizeof(int));
}
ArrayList (ArrayList& list) {
this -> size = list.size;
array = (int *) malloc(list.size * sizeof(int));
}
~ArrayList () {
free(array);
}
};
int main () {
ArrayList list(5);
ArrayList copy = list;
cout << "Original Address: " << list.array << ", Copied Address: " << copy.array << endl;
// => different address!
return 0;
}
Operator Overloading
In some cases, it makes sense to apply algebraic or comparison operators to objects.
class ArrayList {
public:
int* array;
int size;
void print() {
cout << "[";
for (int i = 0; i < size-1; i++) {
cout << array[i] << ", ";
}
cout << array[size-1] << "]" << endl;
}
ArrayList (int array[], int size) {
this -> size = size;
this -> array = (int *) malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
this -> array[i] = array[i];
}
}
~ArrayList () {
free(array);
}
};
int main () {
int array1[5] = {1,2,3,4,5};
int array2[5] = {1,2,3,4,5};
ArrayList list1(array1, 5);
ArrayList list2(array2, 5);
list1.print();
list2.print();
if (list1 == list2) {
cout << "List 1 is the same as List 2!" << endl;
}
ArrayList concat = list1 + list2;
concat.print();
return 0;
}
However, the above code does not compile because the operators for the object are not defined. To fix this, we can define how operators behave for the object, as shown below.
class ArrayList {
public:
int* array;
int size;
void print() {
cout << "[";
for (int i = 0; i < size-1; i++) {
cout << array[i] << ", ";
}
cout << array[size-1] << "]" << endl;
}
ArrayList (int array[], int size) {
this -> size = size;
this -> array = (int *) malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
this -> array[i] = array[i];
}
}
~ArrayList () {
free(array);
}
ArrayList operator+(const ArrayList& array_list) {
int final_size = this -> size + array_list.size;
int final_array[final_size];
for (int i = 0; i < this -> size; i++) {
final_array[i] = this -> array[i];
}
for (int i = size; i < final_size; i++) {
final_array[i] = array_list.array[i - this -> size];
}
return ArrayList(final_array, final_size);
}
int operator==(const ArrayList& array_list) {
if (this -> size != array_list.size) {
return 0;
}
for (int i = 0; i < this -> size; i++) {
if (this -> array[i] != array_list.array[i]) {
return 0;
}
}
return 1;
}
};
Once the operators are defined, concatenation and comparison can be performed using the +
and ==
operators.
Exercises
From this article, there will be an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.
Resources
- Portfolio Courses. 2022. Introduction To Classes And Objects | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. Constructor Basics | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. Destructor Basics | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. Constructor Delegation | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. Define A Copy Constructor To Create A Deep Copy Of An Object | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. Operator Overloading Introduction | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. How To Implement Method Chaining | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. this Keyword And Use Cases Explained | C++ Tutorial. YouTube.
- Portfolio Courses. 2022. Member Initializer Lists | C++ Tutorial. YouTube.