Imagine you're building a robot. You wouldn't want anyone to be able to directly tinker with its internal mechanisms, as it might disrupt its functionality. Instead, you'd provide specific controls that people can use to interact with the robot. This is the concept encapsulation brings to programming in C++.
By default, data within a class is private, meaning you can't access it directly from outside the class. Instead, you provide public methods that define how to interactively and safely use the data. This helps you maintain the integrity of your data and prevent unintended modifications, making your code more secure and robust.
Encapsulation
But why hide them at all? Why prohibit users from directly interacting with the attributes? You can find the answer in the idea of OOP. The object-oriented programming paradigm is based on a general representation of the outside world. If you take any object from real life, it provides some kind of control interface.
Let's consider a couple of examples:
- A car: you don't need to know how the engine works, how the spark plugs and cylinders function, and so on, to drive the car. The car makers have provided us with an interface in the form of a steering wheel and pedals.
- A TV: to turn on the TV or switch channels, you don't need to know how infrared LEDs in the remote work or how electronics, microprocessors, codes, or ciphers function. You just need to know how to use the remote and what each button does (although you can just press them all randomly).
There are many such examples, and in all cases, you use interfaces, leaving complex technical details hidden inside, behind the scenes, thanks to the efforts of developers.
This separation of interface and implementation is extremely useful because it allows you to use objects without needing to understand their implementation. This significantly reduces the complexity of using these devices and significantly increases their number (devices that can be interacted with).
In object-oriented programming, encapsulation (or "information hiding") is the process of secretly storing the details of an object's implementation. Users interact with the object through a public interface (API).
In C++ language, encapsulation is implemented through access specifiers. Typically, all class member variables are private (hiding implementation details), and most methods are public (with a public interface for the user). Although requiring users to use a public interface may seem more burdensome than simply opening access to member variables, it provides many useful advantages that improve code reusability and maintainability.
An access function is a short public function whose task is to get or change the value of a private member variable of a class. There are two types of access functions: setters and getters.
Setters
Setters are functions that allow you to assign values to private class member variables. This is a kind of wrapper for your code that allows you to hide the attributes of your class from unauthorized changes.
Let's look at an example. Let's write a car class skeleton:
#include <iostream>
using namespace std;
class Car {
private:
string m_brand;
string m_model;
double m_engine_power;
public:
void setBrand(string brand) {
m_brand = brand;
}
void setModel(string model) {
m_model = model;
}
void setEnginePower(float engine_power) {
m_engine_power = engine_power;
}
void startEngine() {
cout << m_brand << " starts its engine" << endl;
}
};
int main() {
Car bmwM5;
bmwM5.setBrand("BMW");
bmwM5.setModel("M5");
bmwM5.setEnginePower(600);
bmwM5.startEngine();
return 0;
}
Let's break the code down into its components. We placed all our attributes in the private section:
private:
string m_brand;
string m_model;
double m_engine_power;
Now, if you try to gain access to change somewhere from the code, you will not succeed. The compiler will throw an error. Therefore, you need to create setters and place them in the public section:
public:
void setBrand(string brand) {
m_brand = brand;
}
void setModel(string model) {
m_model = model;
}
void setEnginePower(float engine_power) {
m_engine_power = engine_power;
}
In this case, for each private field, you created your own method for making changes (this was not necessary; perhaps some attributes were changed by other private methods).
set prefix when naming a function; this will make your code easier for other developers (and you, after a while) to understand.Now, you can declare an object and initialize fields in the main() function (as well as perform some actions on the object):
int main() {
Car bmwM5;
bmwM5.setBrand("BMW");
bmwM5.setModel("M5");
bmwM5.setEnginePower(600);
bmwM5.startEngine();
return 0;
}Getters
getters are functions that return the values of private class member variables. It often happens that you need to not only initialize fields and then somehow process them internally but also get the result. Let's add some getters to our example:
string getBrand() {
return m_brand;
}
string getModel() {
return m_model;
}
double getEnginePower() {
return m_engine_power;
}
And you will use them in the main() function:
cout << "Brand: " << bmwM5.getBrand() << endl;
cout << "Model: " << bmwM5.getModel() << endl;
cout << "Engine power: " << bmwM5.getEnginePower() << endl;
get prefix when naming a function. this will make your code easier for other developers (and you, after a while) to understand.As a result, you end up with this program:
#include <iostream>
using namespace std;
class Car {
private:
string m_brand;
string m_model;
double m_engine_power;
public:
void setBrand(string brand) {
m_brand = brand;
}
void setModel(string model) {
m_model = model;
}
void setEnginePower(float engine_power) {
m_engine_power = engine_power;
}
void startEngine() {
cout << m_brand << " starts its engine" << endl;
}
string getBrand() {
return m_brand;
}
string getModel() {
return m_model;
}
double getEnginePower() {
return m_engine_power;
}
};
int main() {
Car bmwM5;
bmwM5.setBrand("BMW");
bmwM5.setModel("M5");
bmwM5.setEnginePower(600);
cout << "Brand: " << bmwM5.getBrand() << endl;
cout << "Model: " << bmwM5.getModel() << endl;
cout << "Engine power: " << bmwM5.getEnginePower() << endl;
bmwM5.startEngine();
return 0;
}
Let's briefly recap what it does:
Caris a class. A class is a basic building block in OOP, which defines a template for creating objects. The Car class describes various characteristics and actions that a car can perform, such asstartEngine.- Objects, such as
bmwM5, are created from theCarclass. They possess all the characteristics and actions described in the Car class. - You set the brand, model, and engine power for
bmwM5, and then call thestartEnginemethod. - Use encapsulation. Encapsulation is one of the main principles of OOP, and it is very well demonstrated in this example. Encapsulation means that an object's data (in this case, the brand, model, and engine power of the car) are hidden from the outside world and are only accessible through the methods of this object (setBrand, setModel, setEnginePower, getBrand, getModel, getEnginePower).
- This is useful because it allows control over access to an object's data and prevents its accidental modification. For example, you can't accidentally set the engine power of
bmwM5to -100, because that doesn't make sense in the real world. If you wanted to implement such control, you could add a check in the setEnginePower method to ensure that the new engine power value is valid.
Importance of encapsulation
Encapsulation has several benefits:
- Encapsulated classes are easier to change: encapsulation allows the implementation details of a class to be hidden. This means that if you need to change how a class works, you can do so without affecting other parts of the program that use the class.
- Encapsulated classes are easier to use and reduce the complexity of your programs: by hiding the internal workings of a class, encapsulation makes the class easier to use and understand. You (or other developers) only need to interact with the API.
- Encapsulated classes help protect your data and prevent it from being misused: encapsulation allows you to control how data in a class is accessed and modified. This can prevent the data from being accidentally or intentionally misused, which can help ensure your programs' integrity and security.
- Encapsulated classes make debugging easier: because encapsulation separates the internal workings of a class from the rest of your program, it can make it easier to identify and fix bugs. You can focus on the behavior of the class in isolation without having to worry about the rest of the program.
Conclusion
In this topic, you've covered encapsulation and the use of getters and setters in C++. Encapsulation has many advantages, the main one being that you can use a class without having to understand its implementation.
- Encapsulation is a fundamental concept in OOP that bundles data and the methods operating on that data into a single unit, a class. It provides a way to protect your data from accidental modification and hides the implementation details of a class.
- Getters and setters are methods used to declare, retrieve, and modify private variables in a class. They are a part of the concept of encapsulation.