Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

(Mostly) Private


Sutter's Mill -- July 2003

In C++, to what extent are the private parts of a class really truly private? In this month's column, we see how private names are definitely not accessible from outside nonfriend code, and yet they do leak out of a class in small ways - some of which are well-known, others of which are not.

[Note: In the previous column, I said I'd talk about inline in this column. I decided to put it off for one column so that this time I can first cover another fun topic: private. Next time it'll be inline's turn.]

"So, just how private is private, Private?" [1]

Quick — assuming that the Twice functions are defined in another translation unit that is included in the link, should the following C++ program compile and run correctly? If no, why not? If yes, what is the output?

// Example 1: Twice(x) returns 2*x
//
class Calc {
public:
  double              Twice( double d );
private:
  int                 Twice( int i );
  std::complex<float> Twice( std::complex<float> c );
};

int main() {
  Calc c;
  return c.Twice( 21 );
}

At the heart of the solution lies this question: In C++, to what extent are the private parts of a class, such as Example 1's Twice, really truly private? In this column, I'll show how private names are definitely not accessible from outside nonfriend code, and yet they can and do leak out of a class in small ways — some of which are well-known, others of which aren't, and a few of which can even be done as a calculated, deliberate act.

The Basic Story: Accessibility

The fundamental thing to recognize is this: Like public and protected, private is an access specifier. That is, it controls what other code may have access to the member's name — and that's all. Quoting from the C++ Standard [2], the opening words of clause 11 state:

"A member of a class can be

  • private; that is, its name can be used only by members and friends of the class in which it is declared.
  • protected; that is, its name can be used only by members and friends of the class in which it is declared, and by members and friends of classes derived from this class (see class.protected).
  • public; that is, its name can be used anywhere without access restriction."

This is pretty basic stuff, but for completeness let's look at a simple example that makes it clear that access is indeed well controlled and there's no standards-conforming way around this. Example 2 demonstrates that nonfriend code outside the class can never get to a private member function by name either directly (by explicit call) or indirectly (via a function pointer), because the function name can't be used at all, not even to take the function's address [3]:

// Example 2: I can't get No::Satisfaction
//
class No {
private:
  void Satisfaction() { }
};

int main() {
  No no;
  <b>no.Satisfaction();             // error</b>

  typedef void (No::*PMember)();
  <b>PMember p = &No::Satisfaction; // error</b>
  return (no.*p)();              // nice try...
}

There's just no way for outside code to incant the name of the function. To the question: "Just how private is private?", we now have the first bit of an answer:

A private member's name is only accessible to other members and friends.

If that were the whole story, this would be a short (and rather pointless) article. But, of course, accessibility is not the whole story.

The Other Story: Visibility

The keyword private does indeed control a member's accessibility. But there is another concept that is related to (but often confused with) accessibility, and that is visibility. Let's return now to the Example 1 program, and the question: Will it compile and run correctly?

The short answer is no. In the form shown, the Example 1 program is not legal and will not compile correctly. There are two reasons why. The first one is a fairly obvious error:

// Example 1 (repeated with annotation)
//
class Calc {
public:
  double              Twice( double d );
private:
  int                 Twice( int i );
  <b>std::complex<float> Twice( std::complex<float> c );
                      // error: std::complex not declared</b>
};

int main() {
  Calc c;
  return c.Twice( 21 );
}

Every C++ programmer knows that, even though the version of Twice that takes a complex object isn't accessible to the code in main, it's still visible and constitutes a source dependency. In particular, even though the code in main can't possibly ever care about complex — it can't even so much as use the name of Twice(complex<float>) (it can't call it or even take its address), and the use of complex can't possibly affect Calc's size or layout in any way — there still at minimum must be at least a forward declaration of complex for this code to hope to compile. (If Twice(complex<float>) were also defined inline, then a full definition of complex would be required too, even though it still couldn't possibly matter to this code.)

To the question: "Just how private is private?", we now have another bit of the answer:

A private member is visible to all code that sees the class's definition. This means that its parameter types must be declared even if they can never be needed in this translation unit...

Everyone knows we can fix this easily enough by adding #include <complex>, so let's do that. This leaves us with the second, and probably less obvious, problem:

// Example 3: A partly fixed version of Example 1
//
#include <complex>

class Calc {
public:
  double              Twice( double d );
private:
  int                 Twice( int i );
  std::complex<float> Twice( std::complex<float> c );
};

int main() {
  Calc c;
  <b>return c.Twice( 21 ); // error, Twice is inaccessible</b>
}

This result surprises a fair number of C++ developers. Some programmers expect that since the only accessible overload of Twice takes a double, and 21 can be converted to a double, then that function should be called. That's not, in fact, what happens, for a simple reason: Overload resolution happens before accessibility checking.

When the compiler has to resolve the call to Twice, it does three main things, in order:

  1. Before doing anything else, the compiler searches for a scope that has at least one entity named Twice and makes a list of candidates. In this case, name lookup first looks in the scope of Calc to see if there is at least one function named Twice; if there isn't, base classes and enclosing namespaces will be considered in turn, one at a time, until a scope having at least one candidate is found. In this case, though, the very first scope the compiler looks in already has an entity named Twice — in fact, it has three of them, and so that trio becomes the set of candidates. (For more information about name lookup in C++, with discussion about how it affects the way you should package your classes and their interfaces, see also Items 31-34 in Exceptional C++. [4])
  2. Next, the compiler performs overload resolution to pick the unique best match out of the list of candidates. In this case, the argument is 21, which is an int, and the available overloads take a double, an int, and a complex<float>. Clearly the int parameter is the best match for the int argument (it's an exact match and no conversions are required), and so Twice(int) is selected.
  3. Finally, the compiler performs accessibility checking to determine whether the selected function can be called. In this case... boom thud splatter.

It doesn't matter that the only accessible function, Twice(double), could in fact be a match; it can never be called, because there is a better match, and a better match always matters more than a more accessible match.

Interestingly, even an ambiguous match matters more than a more accessible match. Consider this slight change to Example 3:

// Example 4(a): Introducing ambiguity
//
#include <complex>

class Calc {
public:
  double              Twice( double d );
private:
  <b>unsigned            Twice( unsigned i );</b>
  std::complex<float> Twice( std::complex<float> c );
};

int main() {
  Calc c;
  <b>return c.Twice( 21 ); // error, Twice is ambiguous</b>
}

In this case, we never get past the second step: Overload resolution fails to find a unique best match out of the candidate list, because the actual parameter type int could be converted to either unsigned or double and those two conversions are considered equally good according to the language rules. Because the two functions are equally good matches, the compiler can't choose between them and the call is ambiguous. The compiler never even gets to the accessibility check.

More interestingly, perhaps, is that even an impossible match matters more than a more accessible match. Consider this rearrangement of Example 3:

// Example 4(b): Introducing plain old name hiding
//
#include <string>

int Twice( int i ); // now a global function

class Calc {
private:
  std::string Twice( std::string s );
public:
  int Test() {
    <b>return Twice( 21 ); // error, Twice(string) is unviable</b>
  }
};

int main() {
  return Calc().Test();
}

Again, we never get past the second step: Overload resolution fails to find any viable match out of the candidate list (which now is only Calc::Twice(string)), because the actual parameter type int can't be converted to string. The compiler again never even gets to the accessibility check. Remember, as soon as a scope is found that contains at least one entity with the given name, the search ends — even if that candidate turns out to be uncallable and/or inaccessible. Other potential matches in enclosing scopes will never be considered.

To the question: "Just how private is private?", we now have yet another bit of the answer:

A private member is visible to all code that sees the class's definition. This means that ... it participates in name lookup and overload resolution and so can make calls invalid or ambiguous even though it itself could never be called.

Bjarne Stroustrup writes about these effect in The Design and Evolution of C++ [5]:

"Making public/private control visibility, rather than access, would have a change from public to private quietly change the meaning of the program from one legal interpretation (access [in our example, Calc::Twice(int)]) to another (access [in our example, Calc::Twice(double)]). I no longer consider this argument conclusive (if I ever did) but the decision made has proven useful in that it allows programmers to add and remove public and private specifications during debugging without quietly changing the meaning of programs. I do wonder if this aspect of the C++ definition is the result of a genuine design decision."

Back to the First Story: Granting Access

As the first part of our answer to the "how private is private?" question, I said that a private member is only accessible to (its name can only be used by) other members and friends. Note that I deliberately avoided saying anything like "'it can only be called by other members or friends," because that's actually not true. Accessibility establishes the code's right to use the name. Let me emphasize that point from the earlier quote from the C++ Standard:

"A member of a class can be

  • private; that is, its name can be used only by members and friends of the class in which it is declared."

If code that has the right to use the name (in this case, a member or friend) uses the name to form a function pointer, and passes that pointer out to other code, the receiving code can use that pointer whether or not the receiving code has the right to use the member's name — it no longer needs the name, because it's got a pointer. Example 5 illustrates this technique at work, where a member function that has access to the name of Twice(int) uses that access to leak a pointer to that member:

// Example 5: Granting access
//

class Calc;
typedef int (Calc::*PMember)(int);

class Calc {
public:
  PMember CoughItUp() { return &Calc::Twice; }

private:
  int    Twice( int i );
};

int main() {
  Calc c;
  PMember p = c.CoughItUp(); // yields access to Twice(int)
  return (c.*p)( 21 );       // ok
}

To the question: "Just how private is private?", we now have one final (at least, final for this article) bit of the answer:

Code that has access to a member can grant that access to any other code, by leaking a (name-free) pointer to that member.

Summary

So, how private is private? Here's what we've found:

A private member's name is only accessible to other members and friends. But code that has access to a member can grant that access to any other code, by leaking a (name-free) pointer to that member.

A private member is visible to all code that sees the class's definition. This means that a private member's parameter types must be declared even if they can never be needed in this translation unit, and it participates in name lookup and overload resolution and so can make calls invalid or ambiguous even though it itself could never be called.

Notes and References

[1] C. Heap, F. Ictional, and O. B. Scure. Military Sounding (Reference, 2003).
[2] ISO/IEC 14882:1998(E), International Standard, Programming Languages - C++.
[3] Undefined hacks like trying to #define private public are nonstandard, deplorable, and reportedly punishable by law in 42 states.
[4] H. Sutter. Exceptional C++ (Addison-Wesley, 2000).
[5] B. Stroustrup. The Design and Evolution of C++ (Addison-Wesley, 1994), page 55.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.