GotW #70

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book Exceptional C++ Style (Addison-Wesley, 2004) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard (1998) and its Technical Corrigendum (2003).

Encapsulation 
Difficulty: 4 / 10

What exactly is encapsulation as it applies to C++ programming? What does proper encapsulation and access control mean for member data -- should it ever be public or protected? This issue focuses on alternative answers to these questions, and shows how those answers can increase either the robustness or the fragility of your code.

Problem

JG Question

1. What does "encapsulation" mean? How important is it to object- oriented design and programming?

Guru Questions

2. Under what circumstances, if any, should nonstatic class data members be made public, protected, and private? Express your answer as a coding standard guideline.

3. The std::pair class template uses public data members because it is not an encapsulated class but only a simple way of grouping data. Imagine a class template that is like std::pair but which additionally provides a deleted flag, which can be set and queried but cannot be unset. Clearly the flag itself must be private so as to prevent users from unsetting it directly. If we choose to keep the other data members public, as they are in std::pair, we end up with something like the following:

template<class T, class U>
class Couple
{
public:
  // The main data members are public...
  //
  T first;
  U second;

  // ... but there is still classlike machinery
  // and private implementation.
  //
  Couple() : deleted_(false) {}
  void MarkDeleted() { deleted_ = true; }
  bool IsDeleted() { return deleted_; }
private:
  bool deleted_;
};

Should the other data members still be public, as shown above? Why or why not? If so, is this a good example of why mixing public and private data in the same class might sometimes be good design?

Solution

1. What does "encapsulation" mean?

According to Webster's Third New International Dictionary:

en-cap-su-late vt: to surround, encase, or protect in or as if in a capsule

Encapsulation in programming has precisely the same sense: To protect the internal implementation of a class by hiding those internals behind a surrounding and encasing interface visible to the outside world.

The definition of the word "capsule," in turn, gives good guidance as to what makes a good class interface:

cap-sule [F, fr. L /capsula/ small box, dim. of /capsa/ chest, case]
1a: a membrane or saclike structure enclosing a part or organ ...
2 : a closed container bearing spores or seeds ...
4a: a gelatin shell enclosing medicine ...
5 : a metal seal ...
6 : ... envelope surrounding certain microscopic organisms ...
9 : a small pressurized compartment for an aviator or astronaut ...

Note the recurring theme in the words:

a) Surround, encase, enclose, envelope: A good class interface hides the class's internals, presenting a "face" to the outside world that is separate and distinct from the internals. Because a capsule surrounds exactly one cohesive group of subobjects, its interface should likewise be cohesive -- its parts should be directly related.

The outer surface of a bacterium's capsule contains its means for sensing, touching, and interacting with the outside world, and the outside world with it. (Those means would be a lot less useful if they were inside.)

b) Closed, seal: A good class interface is complete, and does not expose any internals. The interface acts as a hermetic seal, and often acts as a code firewall (at compile time, runtime, or both) whereby outside code cannot depend on class internals and changes to class internals therefore cause no impact on outside code.

A bacterium whose capsule isn't closed won't live long; its internals will quickly escape, and the organism will die.

c) Protect, shell: A good class interface protects the internals against unauthorized access and manipulation. In particular, a primary job of the interface is to ensure that all access to and manipulation of internal structures is guaranteed to preserve class invariants.

The principal methods for killing bacteria (and humans) involve fashioning devices to break outer and/or inner capsules. On the micro level, these include chemicals, enzymes, or organisms (and possibly eventual nanomachines) responsible capable of making appropriate holes. On the macro level, knives and guns are perennial favorites, and many well-funded lobby groups encourage the "right and duty" to bear capsule-breaching armaments even of types useful only against Kevlar-encapsulated humans of the police variety, on the theory that deer may someday take the anticompetitive action of donning bulletproof vests.

[See United States v. United Antlered Wildlife (UAW) Oregon Local 507, a landmark Department of Justice deer antitrust case currently under appeal. If the DoJ succeeds, the UAW will be divided into two specialized groups, one for soup and one for hunting trophies, neither of which will be permitted to wear vests or initiate other anticompetitive practices. Mr. H. Eston, prominent leader of pro-personal-ICBM group People for the Eating of Tasty Animals (PETA) announced support for the sanctions, stating that hunters have a constitutional right not to have to cope with prey that might defend itself. In related news, in a move apparently designed to defend against accusations of trademark infringement, UAW has just announced that it will soon ship to its members vests made of Protect# (pronounced "Protect- Sharp") which it judiciously describes without using the name "Kevlar" or the letter "K."]

Encapsulation's Place in OO

How important is it to object- oriented design and programming?

Encapsulation is the prime concept in object-oriented programming. Period.

Other OO techniques -- such as data hiding, inheritance, and polymorphism -- are important principally because they support special cases of encapsulation. For example, encapsulation nearly always implies data hiding; runtime polymorphism using virtual functions more completely separates the interface (provided by a base class) from the implementation (provided by the derived class, which need not even exist at the time that the code which will eventually use it is written); compile-time polymorphism using templates completely divorces interface from implementation, as any class having the required operations can be used interchangeably without requiring any inheritance or other relationship. Encapsulation is not always data hiding, but data hiding is always a form of encapsulation. Encapsulation is not always polymorphism, but polymorphism is always a form of encapsulation.

Object-orientation is often defined as:

the bundling together of data and the functions that operate on that data.

That definition is true to a point -- it excludes nonmember functions that are also logically part of a class, such as operator<<() in C++ -- and it stresses high cohesion. It does not, however, adequately emphasize the other essential element of object-orientation, namely:

the simultaneous separation of data from calling code through an interface of functions that operate on that data.

This complementary aspect stresses low coupling, and that the purpose of the assembled functions is to form a protective interface.

In short, object-orientation is all about separating interfaces from implementation in a way that promotes high cohesion and low coupling -- both of which have been known to be sound software engineering goals since long before objects were invented. These concepts address dependency management, which is one of the key concepts in modern software engineering, especially for large systems.[1] [2]

Public, Protected, or Private Data?

2. Under what circumstances, if any, should nonstatic class data members be made public, protected, and private? Express your answer as a coding standard guideline.

Normally we first look at the rule, then at the exception. This time, let's do things the other way around and consider the exception first.

The only exception to the general rule that follows is the case when all class members (both functions and data) are public, as with a C-style struct. In this case the "class" isn't really a full-fledged class with its interface, behavior, and invariants -- it's not even a half-fledged class; it's just a bundle-o-data. The "class" is merely a convenient bundling of objects, and that's fine, especially for backward compatibility with C programs that manipulate C-style structs.

Other than that special case, however, data members should always be private.

Public data is a breach of encapsulation because it permits calling code to manipulate the object's internals directly. This implies a high level of trust! After all, in real life, most other people don't get to manipulate my internals directly (e.g., by operating directly on my stomach) because they might then easily and unintentionally do the wrong thing; at best, they only get to manipulate my internals indirectly by going through my public interface with my knowledge and consent (e.g., by handing me a bottle labeled "Drink Me" which I will then decide to drink, or shampoo my hair with, or wash my car with, according to my own feelings and judgment). Of course, some people really are qualified to manipulate my internals directly (e.g., a surgeon), but even then: a) it's rare; and b) I get to elect whether or not to have the surgery, and if so in which surgeon I will declare the requisite high level of trust.

Similarly, most calling code shouldn't ever manipulate a class's internals directly (e.g., by viewing or changing member data) because they may quite easily and unintentionally do the wrong thing; at best, they only get to manipulate the class's internals indirectly by going through the class's public interface with the class's knowledge and consent (e.g., by handing a Bottle("Drink Me") object to a public member function, which will then decide what, if anything, to do with the object according to the class author's own feelings and judgment). Of course, some nonmember code may really be qualified to manipulate a class's internals directly (usually such code should be a member function, but for example operator<<() cannot be a member), but even then: a) it's rare; and b) the class gets to elect what such outside code will be declared a "friend" with that declaration's attendant high level of trust.

In short, public data is evil (except only for C-style structs).

Likewise, in short, protected data is evil (this time with no exceptions). Why is it evil? Because the same argument above applies to protected data, which is part of an interface too -- the protected interface, which is still an interface to outside code, just a smaller set of outside code, namely the code in derived classes. Why is there no exception? Because protected data is never just a bundle-o-data; if it were, it could only be used as such by derived classes, and since when do you use additional instances of one of your base classes as a convenient bundle-o-data? That would be bizarre. For most on the history of why protected data was originally permitted, and why even the person who campaigned for it now agrees it was a bad idea, see Stroustrup's The Design and Evolution of C++.[3]

From the GotW coding standards:

- encapsulation and insulation:

- always keep class data members private (Lakos96: 65-69; Meyers92: 71-72; Murray93: 33-36)

- except for the special case where all class members are public (e.g., C-style struct)

Related guidelines include:

- encapsulation and insulation:

- avoid returning 'handles' to internal data, especially from const member functions (Meyers92: 96-99)

- where reasonable, avoid showing private members of a class in its declaration: use the Pimpl Idiom

- programming style:

- never subvert the language; for example, never attempt to break encapsulation by copying a class definition and adding a friend declaration, or by providing a local instantiation of a template member function

- design style:

- prefer cohesion:

- prefer to follow the "one class, one responsibility" principle

- prefer to give each piece of code (e.g., each module, each class, each function) a single well-defined responsibility (Items 10, 12, 19, and 23)

A General Transformation

Now let's prove the "member data should always be private" guideline by assuming the opposite (that public/protect member data can be appropriate) and showing that in every such case the data should not in fact be public/protected at all.

// Example 2(a): Nonprivate data (evil)
//
class X
{
  // ...
public:
  T1 t1_;
protected:
  T2 t2_;
};

First, we note that this can always be transformed, without loss of either generality or efficiency, to:

// Example 2(b): Encapsulated data (good)
//
class X
{
  // ...
public:
  T1& UseT1() { return t1_; }
protected:
  T2& UseT2() { return t2_; }
private:
  T1 t1_;
  T2 t2_;
};

Therefore even if there's a reason to allow direct access to t1_ or t2_, there exists a simple transformation that causes the access to be performed through a(n initially inline) member function. Examples 2(a) and 2(b) are equivalent. But is there any benefit to using the method in Example 2(a)?

To prove that Example 2(a) should never be used, all that remains is to show that that:

1. Example 2(a) has no advantages not present in Example 2(b);

2. Example 2(b) has concrete advantages; and

3. Example 2(b) costs nothing.

Taking them in reverse order:

Point 3 is trivial to show. The inline function, which returns by reference and hence incurs no copying cost, will probably be optimized away entirely by the compiler.

Point 2 is easy: Just look at the source dependencies. In Example 2(a), all calling code that uses t1_ and/or t2_ mentions them explicitly by name; in Example 2(b), all calling code that uses t1_ or t2_ mentions only the names of the functions UseT1() and UseT2(). Example 2(a) is rigid, because any change to t1_ or t2_ (e.g., removing them and replacing them with something else, or just tacking on some instrumentation) requires all calling code to be changed to suit. In Example 2(b), however, instrumentation can be added, and t1_ and/or t2_ can even be removed entirely, without any change to calling code, because the member function completes the class's interface and "surrounds," "seals," and "protects" the internals.

Finally, Point 1 is demonstrated by observing that anything that calling code could do with t1_ or t2_ directly in Example 2(a) it can still do by using the member accessor in Example 2(b). The caller may have to write an extra pair of brackets, but that's it.

Let's consider a concrete example: Say you want to add some instrumentation, perhaps something as simple as counting the number of accesses to t1_ or t2_. If it's a data member, as in Example 2(a), here's what you have to do:

1. You create accessor functions that do what you want, and make the data private. (In other words, you do Example 2(b) anyway, only later as a retrofit.)

2. All your users get to experience the joy of finding and changing every use of t1_ and t2_ in their code to the functional equivalent. This is just bound to cause rejoicing among a user community with pressing deadlines who already have other real work to do. Your users may thank you profusely and buy you gifts as a reward; don't open them if they're ticking.

3. All your users recompile.

4. The compile will break if they missed any instances; fix them by repeating steps 2 and 3 until done.

If you already have simple accessor member functions, as in Example 2(b), here's what you have to do:

1. You make the change inside the existing accessor functions.

2. All your users relink (if the functions are in a separate .cpp and not inline), or at worst recompile (if the functions are in the header).

The worst part is that, in real life, if you started with Example 2(a) you may never even be allowed later to make the change to get to Example 2(b). The more users there are that depend on an interface, the more difficult it is to ever change the interface. This brings us to a law (not just a guideline):

Sutter's Law of Second Chances:

The most important thing to get right is the interface. Everything else can be fixed later.

Get the interface wrong, and you may never be allowed to fix it.

Once an interface is in widespread use, there may be so many people who depend on it that it becomes infeasible to change it. True, interfaces can always be extended (added to instead of changed) without impacting anyone, but just adding member functions doesn't help to fix existing parts that you later decide were a bad idea -- at most it lets you add alternative ways of doing things that will confuse your users, who will legitimately ask: "But there are two (or three, or N) ways of doing it... why? Which one do I use?"

In short, a bad interface can be difficult or impossible to fix after the fact. Do your best to get the interface right the first time, and make it surround, seal, and protect its internals.

A Case In Point

3. The std::pair class template uses public data members because it is not an encapsulated class but only a simple way of grouping data.

Note that the above is the exceptional valid use of public data. Even so, std::pair would have been no worse off with accessors instead of public data.

Imagine a class template that is like std::pair but which additionally provides a deleted flag, which can be set and queried but cannot be unset. Clearly the flag itself must be private so as to prevent users from unsetting it directly. If we choose to keep the other data members public, as they are in std::pair, we end up with something like the following:

// Example 3(a): Mixing public and private data?
//
template<class T, class U>
class Couple
{
public:
  // The main data members are public...
  //
  T first;
  U second;

  // ... but there is still classlike machinery
  // and private implementation.
  //
  Couple() : deleted_(false) {}
  void MarkDeleted() { deleted_ = true; }
  bool IsDeleted() { return deleted_; }
private:
  bool deleted_;
};

Should the other data members still be public, as shown above? Why or why not? If so, is this a good example of why mixing public and private data in the same class might sometimes be good design?

This Couple class was proposed as a counterexample to the above coding guidelines. It attempts to show a class that is "mostly a struct" but has some private housekeeping data. The housekeeping data (here a simple attribute) has an invariant. The claim is that updates to the attribute flag are totally independent of updates to the Couple's values.

Let's start with the last statement: I don't buy it. The updates may be independent, but the attribute is clearly not independent of the values, else it wouldn't be grouped together cohesively with them. Of course the deleted_ attribute isn't independent of the accompanying objects -- it applies to them!

Note how, instead of mixing public and private data, we can model the solution using accessors even if the accessors' initial implementation was to give up references:

// Example 3(b): Proper encapsulation, initially
// with inline accessors. Later in life, these
// may grow into nontrivial functions if needed;
// if not, then not.
//
template<class T, class U>
class Couple
{
  Couple() : deleted_(false) { }
  T& First()         { return first_;   }
  U& Second()        { return second_;  }
  void MarkDeleted() { deleted_ = true; }
  bool IsDeleted()   { return deleted_; }

private:
  T first_;
  U second_;
  bool deleted_;
};

"Huh?" someone might say. "Why bother writing do-nothing accessor functions?" Answer: As described with Example 2(b) above. If today calling code can change some aspect of this object (in this example, the tagalong "deleted_" attribute), tomorrow you may well want to add new features even if they do no more than add debugging information or add checks. Example 3(b) lets you do that, and that flexibility doesn't cost you anything in terms of efficiency because of the inline functions.

For example, say that one month in the future you decide that you want to check all attempted accesses to an object marked deleted:

- In Example 3(a), you can't, period -- not without changing the design and requiring all code that uses first_ and second_ to be rewritten.

- In Example 3(b), you simply put the check into the First() and Second() members. The change is transparent to all past and present users of Couple (at worst a recompile is needed; no code changes are needed).

It turns out that Example 3(b) has other practical side benefits in the real world. For example, as Nick Mein (nmein@trimble.co.nz) points out: "You can put a breakpoint (or whatever) in the accessor to find out just where and when the value is being modified. This can be pretty helpful in tracking down a bug."

Summary

Except for the case of a C-style struct (all members public), all data members should always be private. Doing otherwise violates all of the principles of encapsulation noted at the start of this article, and creates dependencies on the data names which makes it harder to later encapsulate them correctly. There is never a good reason to write public or protected data members because they can always be trivially wrapped in (at first) inline accessor functions at no cost, so it's always right to do the right thing the first time.

Get your interfaces right first. Internals are easy enough to fix later, but if you get the interface wrong you may never be allowed to fix it.

 

Notes

1. Robert C. Martin. Designing Object-Oriented Applications Using the Booch Method (Prentice-Hall, 1995).

2. Various 1996 articles by Martin, especially those with "Principle" in the title, at the ObjectMentor website.

3. B. Stroustrup. The Design and Evolution of C++, Section 13.9 (Addison-Wesley, 1994).

Copyright 2009 Herb Sutter