14.1. Private data and classes

I have used the word encapsulation in this book to refer to the process of wrapping up a sequence of instructions in a function, in order to separate the function’s interface (how to use it) from its implementation (how it does what it does).

This kind of encapsulation might be called “functional encapsulation,” to distinguish it from “data encapsulation,” which is the topic of this chapter. Data encapsulation is based on the idea that each structure definition should provide a set of functions that apply to the structure, and prevent unrestricted access to the internal representation.

One use of data encapsulation is to hide implementation details from users or programmers that don’t need to know them.

For example, there are many possible representations for a Card, including two integers, two strings and two enumerated types. The programmer who writes the Card member functions needs to know which implementation to use, but someone using the Card structure should not have to know anything about its internal structure.

As another example, we have been using string and vector objects without ever discussing their implementations. There are many possibilities, but as “clients” of these libraries, we don’t need to know.

In C++, the most common way to enforce data encapsulation is to prevent client programs from accessing the instance variables of an object. The keyword private is used to protect parts of a structure definition. For example, we could have written the Card definition:

struct Card
{
private:
  int suit, rank;

public:
  Card ();
  Card (int s, int r);

  int getRank () const { return rank; }
  int getSuit () const { return suit; }
  void setRank (int r) { rank = r; }
  void setSuit (int s) { suit = s; }
};

There are two sections of this definition, a private part and a public part. The functions are public, which means that they can be invoked by client programs. The instance variables are private, which means that they can be read and written only by Card member functions.

It is still possible for client programs to read and write the instance variables using the accessor functions (the ones beginning with get and set). On the other hand, it is now easy to control which operations clients can perform on which instance variables. For example, it might be a good idea to make cards “read only” so that after they are constructed, they cannot be changed. To do that, all we have to do is remove the set functions.

Another advantage of using accessor functions is that we can change the internal representations of cards without having to change any client programs.

Run the active code below. Uncomment the commented out code to see what happens!

You have attempted of activities on this page