Access Control with public and private
Private members are accessible only through that interface.
Why Some Parts of a Type Stay Hidden
Imagine a collection of numbers kept in one place, with a set of operations that work on those numbers kept somewhere else. That arrangement is flexible, because any operation can touch the data in any way it likes. The flexibility has a cost. Nothing stops a caller from reaching into the storage and changing it in a way the author never intended. A user-defined type that is meant to behave like a built-in type needs a closer relationship between how its data is stored and the operations offered for it. This article uses a class to build that closer relationship and shows which parts of the class are visible to other code and which are hidden.
What do you think happens?
A program creates a Vector object v. Can the program name the internal storage directly, as v.elem, to read or change a value?
Reveal answer
No. elem is a private member, so code outside the class cannot name it directly. The program reaches the stored values only through the public members the class provides.
Access control is what makes this boundary. Public members form the interface that everyone may use, and private members hold the hidden representation. Keep this answer in mind, because the rest of the article traces exactly how the public members reach the private ones.
Interface and Implementation in One Type
A class separates the interface of a type, which everyone may use, from its implementation, which holds the hidden data. The interface is the set of operations a user of the type is allowed to call. The implementation is the stored representation and the code that works on it. In a class, the public members define the interface, and the private members hold the implementation. Private members are accessible only through that interface.
The usual goal is to keep the stored representation out of reach of ordinary users. Hidden storage has three practical benefits. It makes the type easier to use, because users only learn the operations. It lets the author guarantee that the data stays consistent, because every change goes through operations the author controls. It also leaves room to improve the internal representation later without breaking code that depends only on the public operations.
Read the diagram from left to right. The calling program stands outside the class boundary. It can reach the two public members directly. Each public member then reaches the private members that it needs. The caller never has an edge pointing straight at elem or sz, which is exactly what the access rule enforces.
Parts of a Class Definition
A class can contain data members, function members, and type members. Data members hold the stored values. Function members are the operations that work on the data. Type members are named types declared inside the class. Each member is labeled with an access level, either public or private, and that label decides whether code outside the class can use it. The Vector example below uses data members and function members. It does not use a type member, but a type member is still a legitimate part of a class definition.
- The keyword public opens the section that outside code may use: the constructor, size, and the subscript operator.
- The keyword private opens the section that holds the hidden representation: a pointer to storage and a count.
- The constructor takes the number of elements, allocates storage for that many double values, and stores the pointer and the count in the private members.
size()reads the private count and returns it.- operator[] returns a reference into the private storage, so the caller can read or write one element without naming elem.
Tracing a Call Through the Interface
Now follow a short sequence of uses from start to finish. The trace below uses the Vector class from the previous section. Keep track of which step touches the public interface and which step touches the private storage. The diagram shows the order of these interactions.
Tracing a Vector Through Its Public Interface
A program creates a Vector with five elements, asks for its size, and then reads the element at index 2. Which private members change or get read at each step?
- 1Step 1: Create the Vector
The program passes 5 to the constructor. The constructor allocates storage for five double values and stores the pointer in elem and the count 5 in sz. This is the first moment the private members receive values.
- 2Step 2: Call size()
The program calls the public function
size(). That function reads the private count sz and returns it. The caller receives 5 without ever naming sz. - 3Step 3: Subscript with index 2
The program uses the subscripting operator. The operator returns a reference into the private storage at position 2. The caller can read or write that element through the reference, but it still never names elem directly.
The public members did all the work. Creating the object wrote to elem and sz, size() read sz, and the subscript operator gave access to one element of elem. The caller only ever used public names.
Which Names Outside Code Can Use
The access label settles every question about visibility. Public members can be used by any code that has a Vector. Private members can be used only by the members of the class itself. When you predict whether an outside line will compile, check the label on the member it names. The table summarizes the difference.
| Member | Access label | Usable from outside the class? | Role in the design |
|---|---|---|---|
| Vector(int) | public | Yes | Creates and sets up the object |
| size() | public | Yes | Reports the count stored in the private part |
| operator[] | public | Yes | Gives access to one element of the private storage |
| elem | private | No | Holds the pointer to the storage |
| sz | private | No | Holds the number of elements |
Public members form the interface, and private members are reached only through it.
Mistakes Beginners Make with Access Labels
Common mistake Assuming that every member of an object is visible to every caller
What it looks likeA caller writes v.elem to change one stored value.
Why it failselem sits after the private label, so the name is hidden from code outside the class.
Do this insteadUse the public subscript operator, which returns a reference into the private storage.
Common mistake Thinking private members are unusable everywhere
What it looks likeA beginner concludes that elem cannot be used at all and removes the constructor's access to it.
Why it failsPrivate members are fully usable by the class's own functions. The constructor and
size()both depend on them.Do this insteadKeep the private members and let the class's own functions use them.
Common mistake Forgetting that a label controls every member below it until the next label
What it looks likeA programmer places a new function below private: and expects it to be public.
Why it failsThe label applies to all following members until another label appears.
Do this insteadPlace each public operation above the private label, or add a new public: label before it.
Keep the stored representation private so that the author can change it later without breaking code that uses only the public operations. Design the public part first, since it is the contract that users depend on.
Checking Your Understanding of Visibility
Given the Vector class above, which of these outside lines is allowed: (a) v.size(), (b) v.sz, or (c) v[0]? Explain your answer by naming the access label on each member.
Hints
- Look at which names sit after public and which sit after private.
- Think about whether a caller can reach a member through the interface instead of by name.
Trace a new program that creates a Vector with three elements, reads its size, and then reads element 0. For each step, name the public member the caller uses and the private member that the public member touches.
Hints
- The constructor is the first public member to run.
size()reads sz, and the subscript operator reaches elem.
Key Takeaways
- A class separates the interface of a type, which everyone may use, from its implementation, which holds the hidden data.
- Public members define the interface, and private members are accessible only through that interface.
- Data members, function members, and type members all appear inside a class definition, each under an access label.
- A public call such as
size()or the subscript operator reaches private data inside the class, so outside code gets the benefit of the storage without naming it. - Hiding the representation makes a type easier to use, helps keep its data consistent, and allows the internal design to change later.