tags:

views:

479

answers:

5

There are good reasons for constructing the base class interface with all virtual functions as private or protected (see this). But then how does one prevent the derived classes (which may be in the hands of external clients) from making the private virtual function as public? In Virtually Yours, the authors talk about this problem, but no solution is discussed.

Edit: From your answers and as I previously thought, it seems there is no way to prevent this. But since in this situation, it is easy to go wrong (the client surely touches the protected virtual function), it would make sense that the compiler would warn about such usage. I tried to test it with g++. First, I wrote:

class A {
        protected:
        virtual void none() { return; }
};

class B: public A {
        public:
        void none() { return; }
};

g++ -c -Wall -pedantic file.cpp compiled with no error. Adding -Weffc++ gave the warning: warning: ‘class A’ has virtual functions and accessible non-virtual destructor, which makes sense. After adding the virtual destructor, there are no warning. So there is no warning for this easy-to-go-wrong case.

+2  A: 

You can't. "virtualness" of a function and access type are two different unrelated concepts.

Terry Mahaffey
Your answer seems to be saying nothing. Or is it?
Amit Kumar
+2  A: 

Access control in C++ possibly doesn't do what you want. It's not intended to enforce DRM-style constraints to stop you sharing your access. If A has access to B, then A can call B and use the result for any purpose, including returning it to another caller who doesn't have access to B.

The problem that's discussed in the article you link to isn't about A deliberately or maliciously sharing B. It's about what happens if you put a public virtual function in a published interface, and later try to change the class so that it uses their suggested Template Method patterns, including private virtual functions. Child classes have written public overrides of the virtual function, so you can no longer separate the two concerns (access and virtual-ness) without modifying all the child classes. The way I read it, the article does provide a solution to the problem it presents, and that solution is "never make virtual functions public in the first place".

Virtual functions should be treated very much like data members — make them private, until design needs indicate a less restricted approach is indicated. It is much easier to promote them to a more accessible level, than it is to demote them to a more private level.

The reason this doesn't solve your problem, is that they didn't consider your problem.

Steve Jessop
I'd like to bring attention to the extract in the article that says: "Furthermore, derived classes will most likely leave f() in the public interface, and clients that use those derived classes directly." Yes, agreed this is not the main focus of the article, but it is a question, IMO big, nevertheless. Big since it is easy to get wrong here.
Amit Kumar
+9  A: 

As Bjarne put it, the access control in C++ is meant to protect against Murphy, not Machiavelli. The same is true in general -- it's features are meant to protect against accidents, not people intentionally doing something wrong.

To an extent, using C++ means putting at least some degree of trust in the other people who will have access to your source code. If they want to badly enough, they can screw things up in all sorts of ways, and you can't do much of anything to stop them. If you want to place real restrictions on how your code is used, C++ is the wrong language for your purposes.

Edit: This isn't really an "argument" at all -- it's simply pointing out the basis upon which decisions were made. Since I have my copy of the D&E out from answering a previous question, I'll type a bit more if it in here1:

It is more important to allow a useful feature than to prevent every misuse: You can write bad programs in any language. It is important to minimize the chance of accidental misuse of features, and much effort has been spent trying to ensure that the default behavior of C++ constructs is either sensible or leads to compile-time errors. For example by default all function argument types are checked -- even across separate compilation boundaries -- and by default all class members are private. However, a systems programming language cannot prevent a determined programmer from break the system so design effort is better expended on providing facilities for writing good programs than preventing the inevitable bad ones. In the longer run, programmers seem to learn. This is a variant of the old C "trust the programmer" slogan. The various type checking and access control rules exist to allow a class provider to state clearly what is expected from users, to protect against accidents. Those rules are not intended as protection against deliberate violation (§2.10).

In §2.10, he says, among other things:

The task of the protection system is to make sure that any such violation of the type system is explicit and to minimize the need for such violations.

Those goals appear to have been met here -- publicizing a protected base class member definitely requires explicit action in the derived class, and in 20+ years of writing C++ I can't remember ever needing (or even wanting) to do it.

1§4.3, pgs. 115, 116.

Jerry Coffin
I hear that argument a lot, and I'm really starting to dislike it. Yes you can't protect against malicious intent or extreme ignorance, but this argument should not free you of your responsibility to consider reasonable mechanisms/design alternatives that make your code smarter and safer, and less susceptible to accidental misuse.
@STingRaySC: I think it's impossible to formally distinguish between a deliberate exposure of private members (as when an interface has getters and setters, backed up by a private data member), and an accidental one (when somebody incompetently freezes your implementation by adding public getters and setters for a data member which isn't even meaningful in your intended rewrite, but now has to be supported in perpetuity as part of the observable state of the object). So I don't think there's a whole lot the language could do to help here.
Steve Jessop
It makes sense that the compiler would warn in this case (see the edit to my question). I wonder if the standard has anything to say about the situations in which the compiler must warn. Additionally there can be a mechanism similar to java annotation to override the warning in the code (yes, there is pragma, but most pragmas are compiler dependent).
Amit Kumar
I won't classify the client as a Machiavelli in this case, It is quite easy to go wrong. Not sure about "publicizing a protected base class member definitely requires explicit action in the derived class". This case is subtle.
Amit Kumar
+1  A: 

Promoting a private/protected virtual method to public in a derived class does not expose the base class method. It still cannot be called through a base class pointer. It does not become part of the interface of the base clase.

A: 

You could require a token argument which is only constructable by derived types. Of course, then they could just expose a subclass of the token. So you would have to give it a virtual destructor and RTTI check it.

protected:
class ProtectedToken { virtual ~ProtectedToken() { } };
virtual void my_tough_cookie(int arg,
  ProtectedToken const &tok = ProtectedToken() ) {
    assert ( typeid( tok ) == typeid( ProtectedToken ) );
    …
}

Of course this isn't a nice thing to do to anyone, including yourself.

Edit: Bah, it doesn't work. Even if it did, you could do public: using Base::ProtectedToken and defeat the protection that way. Another 15 minutes of my life wasted…

Potatoswatter
Two things: as a default argument it doesn't provide any protection and taking the token by value makes it subject to slicing.
Georg Fritzsche
@gf: A default argument is still constructed by the caller. What do you mean, subject to slicing? Although I forgot it's not really empty, there's the vtable pointer, so it should be a reference… six of one, half dozen of the other, you're going to hell if you do this.
Potatoswatter
@gf: Oh, I see, it will copy-construct a ProtectedToken and the assertion will always succeed.
Potatoswatter
Hmm, why was I under the impression that the caller needs permission for a default argument…
Potatoswatter
Default argument expressions in member function declarations are in class scope, access checking applies accordingly. See the note at the end of *§8.3.6/5* if you're curious.
Georg Fritzsche
@gf: Yeah, I looked it up :vP . I do this compulsively, of course… Probably I was thinking of some experience with template default arguments, which are just unlikely to obtain special access. (§14.7.1 ¶11)
Potatoswatter