Lesson 4
Encapsulation in C++
Lesson Introduction

Hello! Today, we're learning about encapsulation in C++. Encapsulation bundles data and the methods that operate on that data into a single unit called a class. It's used to protect our data from access or modifications from outside the class.

Our goal is to understand encapsulation, how to implement it using private and public members in a class, and why it helps keep our code safe and organized. We'll use an example: solving a quadratic equation and learn to protect critical values.

Understanding Encapsulation

Encapsulation is a core principle of object-oriented programming. It combines data and methods into one unit, called a class. This helps us hide the internal state of the object and only expose what's necessary.

Think of encapsulation like a pill capsule. Inside the capsule, there are different ingredients (data), but when you swallow it, you don't interact with these ingredients directly. Similarly, encapsulation helps control how data is accessed and modified.

In C++, we use private and public access specifiers to implement encapsulation. Private members are variables and methods that cannot be accessed directly from outside the class. We use them to hide the internal state of the object.

The Private Access Specifier
C++
1#include <iostream> 2 3class Example { 4private: 5 int hiddenData; 6}; 7 8int main() { 9 Example obj; 10 obj.hiddenData = 10; // Will cause an error, because hiddenData is private! 11 return 0; 12}

In this example, hiddenData is a private member of the Example class. It is not accessible directly outside the class, and trying to access obj.hiddenData will result in a compilation error.

Step-by-Step Code Walkthrough: Part 1

Let's go through the full code example to see how encapsulation is implemented to solve a quadratic equation.

C++
1#include <iostream> 2#include <cmath> 3#include <stdexcept> // For invalid_argument 4 5class QuadraticEquation { 6private: 7 int a; 8 int b; 9 int c; 10 11public: 12 // Constructor 13 QuadraticEquation(int a, int b, int c) { 14 if (a != 0) { 15 this->a = a; 16 } else { 17 throw std::invalid_argument("Coefficient a cannot be zero."); 18 } 19 this->b = b; 20 this->c = c; 21 }

Notice that we have declared a, b, and c as private members. This is the essence of encapsulation; we're hiding these members from direct access outside the class. Importantly, to protect our class from setting a to zero (which would result in a division by zero during calculation), we make a private. By doing this, we prevent unintended modifications from external code. Though b and c can take any values and don't require special protection, it is common to make all the class attributes private to ensure we can control access to them in the future.

Step-by-Step Code Walkthrough: Part 2

But how do we set and get the attribute if its private? Let's take a look at this code snippet to see:

C++
1 // Public method to set the value of a 2 void setA(int a) { 3 if (a != 0) { 4 this->a = a; 5 } else { 6 throw std::invalid_argument("Coefficient a cannot be zero."); 7 } 8 } 9 10 // Public method to get the value of a should be const as it does not modify anything 11 int getA() const { 12 return a; 13 }

We then provide public getter and setter methods for a. This allows controlled access and modification:

  • Getter (getA): Allows external code to read the value of a. Note that we marked the getter const as it is not supposed to modify the class fields. This const keyword guarantees this method won't change the object's parameters.
  • Setter (setA): Allows external code to modify the value of a, but only if the new value is not zero. This validates the value and prevents invalid scenarios like division by zero.

Similarly, we can define methods getB, setB, getC, setC to access other fields.

Step-by-Step Code Walkthrough: Part 3

Finally, we define the solve method that provides an answer:

C++
1 // Public method to solve the quadratic equation should be const as it does not modify anything 2 void solve() const { 3 double discriminant = b*b - 4*a*c; 4 if (discriminant > 0) { 5 std::cout << "Two Real Roots: " << (-b + std::sqrt(discriminant)) / (2*a) << " and " << (-b - std::sqrt(discriminant)) / (2*a) << std::endl; 6 } else if (discriminant == 0) { 7 std::cout << "One Real Root: " << -b / (2*a) << std::endl; 8 } else { 9 std::cout << "No Real Roots" << std::endl; 10 } 11 } 12};

Here, we can be sure that a will never equal zero, and the division by zero won't occur.

Step-by-Step Code Walkthrough: Part 4
C++
1int main() { 2 // Creating an object of QuadraticEquation 3 QuadraticEquation equation(1, -3, 2); 4 equation.solve(); // Output: Two Real Roots: 2 and 1 5 6 // Trying to update a 7 equation.setA(2); 8 equation.solve(); // Output: No Real Roots 9 10 return 0; 11}

In the main function, we create an object of QuadraticEquation with initial values a = 1, b = -3, and c = 2. Then, we call the solve method to output the roots. We demonstrate modifying a through the setter method, ensuring the update follows our rule of a being non-zero.

Benefits of Encapsulation

Encapsulation offers several benefits:

  • Data Security: Protects data from unintended modifications.
  • Code Maintainability: Makes code easier to manage and understand. We can change the internal implementation without affecting the rest.
  • Controlled Modification: Ensures changes to data only happen through controlled methods.
Lesson Summary

Great job! We've learned that encapsulation bundles data and methods into a single unit (a class) while hiding the internal state from direct access. By using private and public members, we control how data is accessed and modified.

In our quadratic equation example, encapsulation protected the coefficient a, ensuring it wasn't set to zero.

Now it's time to practice what we've learned. In the upcoming exercises, you'll create your own classes that use encapsulation to protect and manage data. Get ready to apply these concepts!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.