Chapter 4: Classes#
C++ is an object oriented programming language, where classes form a principal role in structuring software in a manageable, efficient, and reusable manner. This programming paradigm is intuitive and its use is widespread[1].
In this chapter, we will take a deeper look at classes and what differentiates them from what you have seem in Python.
What We’ve Seen Before#
We saw that classes can have public and private methods and attributes, each with a different level of accesibility. Their accesibility is re-sumarized in the following list
public
: Members are accessible from outside the classprivate
: Members are accesible only inside the class.
In addition, we saw that when we instantiate a class, a method called the “contructor” is called which has the purpose of initializing the object if neccesary. The following example summarizes all of these things.
1#include <iostream>
2using namespace std;
3
4class MyClass
5{
6 public:
7 // Constructor, equivalent of __init__ in Python
8 MyClass(int val) : value(val) {}
9
10 void display()
11 {
12 cout << value << endl;
13 }
14 private:
15 int value;
16};
17
18int main()
19{
20 MyClass obj(10);
21 obj.display();
22 return 0;
23}
Inheritance#
Inheritance is a fundamental concept in OOP. It describes the capacity for a class to acquire methods and attributes from another. The base class from which other class(es) derive from is called the parent/base class, while those classes that inherit from the parent class are called the child/derived class. This is very useful because it allows you to define a generalized base class which works for many different sitautions, and then have derived classes which inherit from the base class to work in a more specialized case. For instance, you could define a base class called vehicle
, and then have children classes such as car
, bus
and motorcycle
which inherit from vehicle
. This key feature in OOP allows us to structure our code in a logical fashion which is easier to understand.
The following example illustrates the use of inheritance between two classes in C++.
1#include <iostream>
2using namespace std;
3
4// Base class
5class Particle
6{
7public:
8 float mass;
9
10 // Constructor
11 Particle(float mass, float quirkiness, float meanlifetime)
12 : mass(mass), quirkiness(quirkiness), meanlifetime(meanlifetime)
13 {
14 cout << "Particle Constructor Called. Assigning mass, quirkiness, and mean lifetime..." << endl;
15 }
16
17 void ParticleFunction()
18 {
19 cout << "Function of Particle Class. Just printing!" << endl;
20 }
21
22private:
23 float quirkiness;
24
25protected:
26 float meanlifetime;
27};
28
29// Derived class
30class Muon : public Particle
31{
32public:
33 int charge;
34 float spin;
35
36 // Constructor
37 Muon(int charge, float spin, float mass, float quirkiness, float meanlifetime)
38 : charge(charge), spin(spin), Particle(mass, quirkiness, meanlifetime)
39 {
40 cout << "Muon Constructor Called. Assigning charge and spin." << endl;
41 }
42
43 void MuonFunction()
44 {
45 cout << "Function of Muon Class. Just printing!" << endl;
46 }
47
48 void PrintLifeTime()
49 {
50 cout << "Mean Lifetime: " << meanlifetime << endl; // Accessing protected member from base class
51 }
52};
53
54int main()
55{
56 Muon mu(1, 0.5, 0.1057, 1.0, 10.0);
57 mu.MuonFunction();
58 mu.ParticleFunction();
59 cout << "Charge: " << mu.charge << endl;
60 cout << "Spin: " << mu.spin << endl;
61 cout << "Mass: " << mu.mass << endl;
62 mu.PrintLifeTime(); // Calling function to print meanlifetime
63 return 0;
64}
Particle Constructor Called. Assigning mass, quirkiness, and meanlifetime...
Muon Constructor Called. Assigning charge and spin.
Function of Muon Class. Just printing!
Function of Particle Class. Just printing!
Charge: 1
Spin: 0.5
Mass: 0.1057
Mean Lifetime: 10
In this example, Particle
is a parent class from which Muon
inherits. As can be observed, in the main
body of the code we were able to call a public method that belonged to Particle
as if it belonged to the Muon
class. In addition, we called the method PrintLifeTime()
which accessed Particle
’s protected attribute meanlifetime
. WHen a method or attribute is protected
, it can only be accesed in two ways: through the class itself, or through one of its derived classes that inherit from it. This means that if we had tried to do mu.meanlifetime
, I would have gotten an error. On the other hand, the private attribute quirkiness
can only be accessed in the same class it was defined, which means that Muon
cannot access it directly. However, it is possible for Muon
to access it indirectly by calling a protected or public method which in Particle
which accesses meanlifetime
.
You might ask yourself: “That’s nice, but I can code in Python without having to have any private, public or protected method/attributes and it works perfectly fine. So what’s the point!” While its true that its not neccesary for a programming language to have these features, having them does offer some benefits. For instance, it allows us to control access to certain critical methods or attributes, thus enhancing the security of our code. Additionally, it can help with controlling what exactly is inherited to a derived class and with having a stable API for users despite changes to the parent class definition itself.
Polymorphism#
Polymorphism allows methods to do different things based on the object is is acting on. The main idea is that you want to be able to use a function with different types of inputs without having to define a function for every possible type of input. Polymorphism is achieved primarily through the use of virtual function and abstract classes.
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "Some animal sound!" << endl;
}
};
class Dog : public Animal
{
public:
void speak() override
{
cout << "Woof!" << endl;
}
};
void makeAnimalSpeak(Animal& animal)
{
animal.speak();
}
int main()
{
Animal animal;
Dog dog;
makeAnimalSpeak(animal); // Outputs: Some animal sound!
makeAnimalSpeak(dog); // Outputs: Woof!, thanks to polymorphism
return 0;
}
Destructor#
A destructor is another special function that is automatically called when an object goes out of scope or is deleted. Its purpose is to free sources allocated during the object’s lifetime, such as memory and file. These type of class method exists in Python, but it is not as essential as in C++ because Python has a garbage collector that takes care of these things in the background automatically[2].
If we forget to include a destructor, one is created for us by default. This is mostly okay, but if your class handles pointers, we should write a destructor ourselves. Not doing this could lead to memory leaks.
Here is a quick example of a destructor in use.
1#include <iostream>
2using namespace std;
3
4class DynamicArray
5{
6public:
7 // Constructor
8 DynamicArray(int size) : m_size(size)
9 {
10 m_array = new int[m_size]; // Allocate memory for the array
11 cout << "Array of size " << m_size << " created." << endl;
12 // Initialize the array for the sake of completeness
13 for (int i = 0; i < m_size; ++i) {
14 m_array[i] = i * i; // Storing square of the index
15 }
16 }
17
18 // Destructor
19 ~DynamicArray()
20 {
21 delete[] m_array; // Release the allocated memory
22 cout << "Array of size " << m_size << " destroyed." << endl;
23 }
24
25 // A function to print the array elements
26 const void printArray()
27 {
28 for (int i = 0; i < m_size; ++i) {
29 cout << "Element at index " << i << ": " << m_array[i] << endl;
30 }
31 }
32
33private:
34 int* m_array; // Pointer to the dynamically allocated array
35 int m_size; // Size of the array
36};
37
38int main()
39{
40 DynamicArray arr(5); // Create an object of DynamicArray
41 arr.printArray(); // Print the array elements
42
43 // When the function exits, arr goes out of scope, and its destructor is automatically called
44 return 0;
45}
If you’re ever asking yourself whether you need to explicitly define a destructor for your class, ask yourself this: “Does my class manage any resources, such as dynamically allocated memory, file handles, or network connections?” If it does, you should explicitly define a destructor. This is because built-in data types and standard library classes like std::string
or std::vector
have destructors that automatically manage their resources. However, if your class directly manages resources like pointers, these resources will not be automatically released when the object goes out of scope, which can lead to memory leaks and resource leaks. Therefore, defining a destructor allows you to ensure proper cleanup of these resources.
Exercise 4.1
Write a program that allows the user to get information about different types of particles (Electron, Muon and Tau in particular) and compute their energy given a fraction of the speed of light they are traveling at. The requirements are the following:
I/O
Use
cin
to get input from the user.use
cout
to display results to the user.
Decision making:
Use
if-else
to handle different user inputs.
Functions:
Create functions to display particle information.
Create a function to compute the energy of a particle given a fraction of the speed of light.
Classes and inheritance:
Implement a base class
Particle
with pure virtual functions. Implement derived classesElectron
,Muon
andTau
that inherit fromParticle
.
Access specifiers
Use
public
,protected
andprivate
access specifiers wherever neccesary and appropriate.
You can find some of the code already done in exercises_cpp/c4_1.cpp
to get you started!