Restricting Access to C++ Classes

Make our classes easier to use and less prone to bugs: an introduction to encapsulation
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art of a character in a prison cell
Ryan McCombe
Ryan McCombe
Edited

We've now successfully abstracted our Monster objects into their own class. This is keeping our code nice and organised.

Lets consider what our class might look like:

class Monster {
public:
  int Health { 150 };
  bool isDead { false };

  void TakeDamage(int Damage) {
    Health -= Damage;
    if (Health <= 0) {
      Health = 0;
      isDead = true;
    }
  };
};

So far, so good. We have a function that allows our monster to TakeDamage. The function adds some constraints - it ensures the Health cannot be negative, and it ensures that the isDead boolean is updated at the appropriate time.

However, there are some issues with this class already. The concept of encapsulation is designed to solve these issues.

Why we need Encapsulation

Whilst our function is ensuring our object variables get updated as we desired, our class itself is not.

Anyone using our objects can still just get the object into an invalid state:

Monster Harry;

// Health will now be negative :(
Harry.Health -= -200;

// true! a monster with negative health is still alive?
Harry.isDead;

Monster Julia;
Julia.isDead = true;

// 150! how is a dead monster at full health?
Julia.Health;

The problem is that our class is a little too open at the minute. Code from outside of our class can bypass our function and directly modify parts of the object in unintended ways.

This is where encapsulation can help.

What is Encapsulation

To understand encapsulation, and why it is valuable, lets consider a real world object - something like a car.

Cars are complex machines - there are gears, electrics, hydraulics and many other complications that are necessary to make the vehicle work.

Yet, to drive the car, we don't need to know how it works. We don't drive the car by manipulating the gears and hydraulics directly. That would be unpleasant, and dangerous.

Instead, manufacturers hide all the complexity away. We are just presented with a simple interface that is much easier and safer to use - a steering wheel and pedals.

We have similar aims when designing software. We want to make our classes as easy and safe to use as possible. We do that by hiding away all the complexity.

Test your Knowledge

What is encapsulation?

We've already been using a form of encapsulation. Consider what a function is. A function is a way to hide (or encapsulate) a block of code inside a nice, friendly package.

A function body can get as complicated as needed - it can have hundreds of lines of code, maybe dozens of nested function calls.

Yet, for a developer using the function, all that is hidden away. All they need to do is write a single line of code to call the function, and trust that it will work.

We have similar goals with our class design. Imagine the complexity in making a monster move in a 3D game. That involves complicated vector maths, physics, pathfinding code, animation, sound, and more.

To support all that, we would need to store a bunch of variables and have a load of functions in our class. But, we don't want external code from outside our class messing with those complex inner workings. Only our class code should be managing those variables.

The people writing that external code don't want that, either. They just want to make our object move, in the easiest way they can. The best solution for everyone is that, any time we want the monster to move, we just call a function like Monster.Move(NewPosition)

The people using our class have a nice experience and we, as the creators of the class, only need to support a single way of making our objects move.

Once we have our nice, friendly function, we then want to hide all the inner workings away, to make sure everyone knows this is the only way they should be making our object move.

This is the essence of encapsulation, and it is one of the most important principles for making larger software.

  1. We create some functions that we want the outside world to use when they need to interact with our objects.
  2. We restrict access to everything else, to prevent people bypassing those functions to manipulate our objects in unintended ways.

Public and Private Class Members (C++)

To express this in C++ terminology, we want to have part of our class be public and part of it to be private.

The parts of the class that we want external code to use will be public. This will be the friendly, external-facing interface.

The parts that we don't want people messing with will be private.

We saw in the class definition above, we already have the word public in our code:

class Monster {
public:
  int Health { 150 };
  bool isDead { false };

  void TakeDamage(int Damage) {
    Health -= Damage;
    if (Health <= 0) {
      Health = 0;
      isDead = true;
    }
  };
};

In classes, all the members are private by default. What we were doing here was making everything public. Any code that creates a new object from our class is able to access and change everything on that object. We had no encapsulation.

This is the root cause of the flaw we demonstrated in the previous section where code set a monster's health to 0, but did not set isDead to true.

Lets update our class to make this impossible, by introducing a private section

class Monster {
public:
  void TakeDamage(int Damage) {
    Health -= Damage;
    if (Health <= 0) {
      Health = 0;
      isDead = true;
    }
  };

private:
  int Health { 150 };
  bool isDead { false };
};

Private members of a class can still be modified by the functions of the class. Our class functions like TakeDamage will be able to modify the Health and isDead values, but code outside our class will no longer have access to it.

Now, the public interface is very simple and protects users of our class from sneaking past our intended behaviour

Monster Goblin;
Goblin.TakeDamage(50); // This is allowed
Goblin.Health -= 50;  // This isn't

Test your Knowledge

In the following example, what is the first line in the code that will cause an error?

1class Monster {
2public:
3  int Health{500};
4};
5
6int main(){
7  Monster Vampire;
8  Vampire.Health;
9  Vampire.Health++;
10}
11

What is the first line in the code below that will cause an error?

1class Monster {
2private:
3  int Health{500};
4};
5
6int main(){
7  Monster Vampire;
8  Vampire.Health;
9  Vampire.Health++;
10}
11

After refactoring our previous class to move Health to the private section, we are now perhaps being a bit too restrictive.

We've prevented our health being modified directly, but code outside our class also can't tell how much health our monsters have.

That seems like it will be an unreasonable limitation, so lets see a way that we can fix that.

Getters and Setters for C++ Classes

The typical approach for allowing external code access to our private members is to use accessor functions, sometimes called getters and setters.

Getters and setters are not special type of functions. They are just regular functions.

The concept of getters and setters is simply a design pattern. They are a way to give the outside world access to private members, but in a way that we can control from our class code.

What follows is the most basic example of a getter:

class Monster {
public:
  int GetHealth() { return Health; }

private:
  int Health { 150 };
};

This allows outside code to see the current Health using the public GetHealth function. But, they cannot change the Health, because the variable itself is still private:

Monster Harry;

// External code can now see the health
Harry.GetHealth();

// But they cannot change it directly
Harry.Health += 50;

Setters have a similar purpose to getters except, predictably, they are functions that allow the outside world to set properties on our object.

The difference between making a setter available and just making the underlying variable public is that, with a setter being a function, we can control the process.

Lets imagine we did want the ability to set a monster's health to be public, but we wanted to apply some restrictions:

  • Health can never be negative
  • If Health is 0, the monster should be dead
  • Otherwise, the monster should be alive

We couldn't ensure this if we just made Health public. Instead, we can keep Health private, but make a public function available to set its value:

class Monster {
public:
  void SetHealth(int NewHealth) {
    if (NewHealth <= 0) {
      Health = 0;
    } else {
      Health = NewHealth;
    }
    isDead = Health == 0;
  }

private:
  bool isDead { false };
  int Health { 150 };
};

Test your Knowledge

Line 7 in the below code example is an error, as Speed is a private member. How should we modify our class to allow line 7 read the car's speed, but not change it?

class Car {
private:
  int Speed { 50 };
}

Car MyCar;
MyCar.Speed;

We've discussed a few techniques now around how to protect our objects from getting into an invalid state.

By rethinking our class slightly, we can fix this issue at the root, and make our code simpler at the same time.

This process is called refactoring, and will be the topic of our next lesson.

Was this lesson useful?

Edit History

  • Fixed syntax errors in two quiz questions

  • First Published

Ryan McCombe
Ryan McCombe
Edited
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Classes
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

Refactoring C++ Classes

See how we can use refactoring to keep our code clean, simple and easy to update
3D art showing a maid cleaning
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved