Pete explains why base classes sometimes bar the door to their descendants, and the true meaning of orthogonality.
To ask Pete a question about C or C++, send e-mail to petebecker@acm.org, use subject line: Questions and Answers, or write to Pete Becker, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.
Q
Please explain the compiler behavior on the following piece of code:
class A { protected: char ch; }; class B : public A { public: bool f( const A& a ) { return ch == a.ch; } bool g( const B& b ) { return ch == b.ch; } };This code, if compiled with Visual C++ 5.0, gets the error message "error C2248: 'ch' : cannot access protected member declared in class 'A'" even though B is publicly derived from A just because A is passed as an argument to f. The same will not occur in the case of g. I get a similar message from Borland C++ 5.0. I guess this is the correct compiler behavior, but I cannot understand why.
Vassilis Kontopanos
A
Although I'm not a fan of compiler polls as a way of determining correct behavior, in this case your poll led you to the right conclusion: the attempted access in f is not permitted. In general, though, it's better to approach this sort of problem by looking at the language definition. Although the standards committee recently approved the Final Draft Information Standard, it's going to take a few months more before there is a formal C++ standard, so you'll have to wait a while before you can get the actual text. However, in the meantime you can find text that's quite close to what will be in the standard. There's a late 1996 version of the working paper available at www.cygnus.com/misc/wp/dec96pub. That's the version that was sent out to the various national bodies for the penultimate ballot, and the comments on that version drove the changes that were made to produce the FDIS. It's probably the best source of information you can get if your company is not a member of ANSI or ISO.
For this particular question, however, the critical part of the language definition hasn't changed in years, and the ARM [1] is still accurate. The rule is:
When a friend or a member function of a derived class references a protected nonstatic member of a base class, an access check applies in addition to those described earlier . . . Except when forming a pointer to member, the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class) [2].
In your member function B::g, the expression b.ch attempts to access the protected member ch of class A through a reference to an object of type B. That's allowed, because B is derived from A, and we're in a member Function of B. In the member function B::f, the expression a.ch attempts to access ch through a reference to an object of type A. That's not allowed, because A is not the derived class A, and that violates the final sentence quoted above.
Prohibiting this access might seem somewhat arbitrary, but it's really not. Here's the sort of problem that it's intended to avoid:
class C : public A { }; int main() { B b_obj; C c_obj; b_obj.f(c_obj); return 0; }
The problem is that C's designer may be using that field ch in a different way than B uses it. Allowing member functions of B to access it allows modification in ways that C's designer did not allow for. It's hard enough to design classes so that they are robust when new classes are derived from them. It's much harder to design classes that are robust when an unrelated class can access your base parts. This is really a sensible rule of encapsulation; it's there to keep your classes healthy.
On page 78 of the January 1998 issue of C/C++ Users Journal, you wrote ". . . after all, the principle of orthogonality demands that we be able to random_shuffle a string ..." in your answer to Christopher Madsen's question regarding strings. Pardon my ignorance, but what's the principle of orthogonality? The dictionary definitions of orthogonal didn't help me puzzle out the meaning myself, and I've not encountered a reference to this principle in my brief career with C++. Thanks.
Preston Black
My dictionary defines "orthogonal" as "having a sum of products or an integral that is zero or sometimes one under specified conditions."[3] Doesn't that make it perfectly clear? No? Well, maybe the final definition that it gives helps a bit more: "statistically independent." That is, when you change one item, things that are orthogonal to that item aren't changed. I was using the term a bit loosely, but the point was that STL containers bring in many things that aren't very closely related to a string's intended use. Rather than adding a set of operations that makes sense for a string, the decision to make a string into an STL container brings in a set of operations that's not very closely related to the set of things that the string class needs to do. In particular, you can now apply the algorithm random_shuffle to a string, something which I consider to be utterly useless. But I don't want to fight that battle any longer I wasn't able to convince the other members of the committee that this was a bad idea, and now that the standard is finished, it's there for all of us to use. The string class is very powerful. Be sure, when you use it, that you're clear on whether you're thinking of it as an array of characters with memory management, a text manipulator, or an STL container. If you mix these concepts together you'll be confused. If you're clear on what you're trying to do, the string class supports all three of these modes of use.
Hunter Swade
As explained in the previous question, access restrictions and virtualness are orthogonal. Here's a bit of code to demonstrate the issues:
#include <iostream> class Base { public: void f() const { g(); } private: virtual void g() const { std::cout << "Base::g\n"; } }; class Derived : public Base { private: virtual void g() const { std::cout << "Derived::g\n"; } }; int main() { Derived d; d.f(); return 0; }
If you pretend that everything in both classes is public, this program is easy to understand: the call d.f() calls Base::f, which in turn calls g(). Since Base::g is declared virtual, this call actually calls Derived::g, which prints out "Derived::g" on the console.
If you pay attention to the access restrictions, however, it's not immediately clear that this is what ought to happen. In fact, the access restriction in Derived is the basis for many people's discomfort with this: since Derived::g is private, Base::f shouldn't be able to call it. That's an oversimplification of what access specifiers mean. Consider this example:
class Sample { public: int& get_x() { return x; } private: int x; }; int main() { Sample s; s.get_x() = 3; return 0; }
Here's a class named Sample with a private member x. The designer of the class decided to make this member accessible by returning a reference to x from get_x. That's valid, and it does just what you'd expect it to do. The fact that x is private doesn't make it invalid it's the designer's choice to expose it this way. You might want to question the designer's skill if you see something like this, because the need for it is rather rare [4], but there's nothing invalid about it.
You can do the same sort of thing with function pointers:
class Callee; typedef void (Callee::*pf)(); class Callee { public: pf get_func() const { return &Callee::f; } private: void f() { std::cout << "Callee:f\n"; } }; class Caller { public: void call_it( Callee *obj, pf func ) { (obj.*pf)(); } }; int main() { Callee ce; Caller cr; cr.call_it( &ce, ce.get_func() ); return 0; }
Callee provides a member function, get_func, which returns a pointer to a private member of Callee. Caller has a member function that takes two arguments: a pointer to an object of type Callee and a pointer to a member function of Callee. It calls the member function on the pointed-to object. This is perfectly valid, even though the function that's being called is private. A member function of Callee made it available, and the compiler won't look behind your code and tell you that you shouldn't allow anyone else to call that function [5].
In each of these cases, the code bypasses the guarantees made by the private access restriction. In each case the bypass is deliberate and controlled. The same applies when writing a class that overrides a private virtual function. It's not quite as private as you may have thought, but that loss of privacy is tightly controlled: the only additional way to call that function is by calling the corresponding function in the base class, and that's controlled by the access restrictions in the base class. If you don't want that to happen, don't override the function.
When would you use a private virtual function? Return to that ancient example of polymorphism, the shape:
class Shape { public: Shape() : x(0), y(0) {} void move( int newX, int newY ) { x = newX; y = newY; } void draw() { draw_shape(x,y); } private: int x,y; virtual void draw_shape(int,int) = 0; }
The idea is to provide an abstract base class that allows creating various shapes, moving them around on a canvas, and drawing them. The code for moving a shape doesn't depend on the actual shape involved, so that code goes in the base class. You do need different code to draw each of the different shapes, so that code can't be in the base class. However, some of the information that's needed to draw a shape is in the base class, so I've implemented draw by calling draw_shape and passing the position of the shape. Each derived class is responsible for implementing draw_shape(int,int) appropriately [6]:
class Square : public Shape { public: Square( int ix, int iy, int s ) : Shape(ix,iy), side(s) {} private: void draw_shape( int x, int y ) { moveto( x-s/2,y+s/2 ); drawto( x+s/2,y+s/2 ); drawto( x+s/2,y-s/2 ); drawto( x-s/2,y-s/2 ); drawto( x-s/2,y+s/2 ); } };
Of course, when you make a virtual function private you prevent derived classes from calling that function. In particular, a function that overrides your virtual function cannot use your virtual function to do part of its work:
class TWindow { protected: virtual void show() { // code to show client area // goes here } }; class TBorderedWindow : public TWindow { protected: virtual void show() { Twindow::show(); // code to show border // goes here } };
If TWindow::show had been private, TBorderedWindow::show wouldn't have been able to call it and would have had to re-implement the code for showing the window's client area. Rather than do that, show is declared protected in TWindow so that TBorderedWindow::show can reuse it. o
1. Margaret A. Ellis, and Bjarne Stroustrup. The Annotated C++ Reference Manual (Reading, MA: Addison-Wesley, 1990).
2. "Working Paper for Draft Proposed International Standard for Information Systems Programming Language C++," X3J16/96-0225, WG21/N1043. December 2, 1996.
3. Britannica CD, Version 97 (Encyclopaedia Britannica, Inc., 1997).
4. Please don't ask me for an example of when you might need this. I haven't put together a coherent example, but I suspect I could come up with a reasonable one if I worked on it.
5. Incidentally, you can run into the same sort of confusion in C. In one translation unit you have a static function, and you take its address and pass the address to code in another translation unit. That code calls the function. Nothing wrong here, even though many people would say that static functions can't be called from another translation unit.
6. I was tempted to name this function draw, too, using overloading to pick the right one. That would work, but could be a bit confusing. In real code I'd probably do it that way, because those two functions really do the same thing in a meaningful sense. For this column, I've opted to keep things simpler and use a distinct name.
Pete Becker is Technical Project Leader for Dinkumware, Ltd. He spent eight years in the C++ group at Borland International, both as a developer and manager. He is a member of the ANSI/ISO C++ standardization commmittee. He can be reached by email at petebecker@acm.org.