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.
July 01, 2003
URL:http://drdobbs.com/mostly-private/184403867
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 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
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; no.Satisfaction(); // error typedef void (No::*PMember)(); PMember p = &No::Satisfaction; // error 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 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 ); std::complex<float> Twice( std::complex<float> c ); // error: std::complex not declared }; 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; return c.Twice( 21 ); // error, Twice is inaccessible }
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:
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: unsigned Twice( unsigned i ); std::complex<float> Twice( std::complex<float> c ); }; int main() { Calc c; return c.Twice( 21 ); // error, Twice is ambiguous }
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() { return Twice( 21 ); // error, Twice(string) is unviable } }; 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."
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
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.
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.
[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.
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.