Topic: misuse of polymorphism???


Author: "David Dillard" <ddillard@usa.net>
Date: 2000/06/02
Raw View
"Robert Spatschek" <spatschk@physik.rwth-aachen.de> wrote in message
news:39356F41.A9E96A74@physik.rwth-aachen.de...
> Nevertheless I'm still curious about an explanation WHY this kind of
> inheritance is allowed in the standard. Which sense does it make NOT to
> forbid the transition from public (in A) to private (in B) if it is
> still possible to call the (now private) function from a non-member
> function g()? It is not compatible with my personal understanding of
> encapsulation. As you explained in a very clearly there is no
> possibility to detect this during compilation because the virtual
> function call is resolved at run time. Thus the only place where the
> contradictions (?) can be found is in the definition of the class B.
>
> Actually I guess that I simply overlook something. There must be a good
> reason why this constructions makes sense though, but I can't see it. I
> hope that somebody can help me.

Here's an example of where I've done something very similar and why.  It
provides a crude, unenforceable method of access security.

In my case I had a read-only interface from which I inherited a read/write
interface.  A class would inherit from this read/write interface for the
actual implementation.

Some clients should be able to change the object, others should not hence
the need for two interfaces.  Originally, the two interfaces were separate
(one read interface, one write interface), but that was a big hassle so the
change was made.  It seemed like a reasonable change to make anyway, after
all the only people who can't read what they've written work for the CIA.

The class declares the methods of the read interface as public because any
client can read from the object.  However, the class declares the methods of
the write interface as protected because any client should not be able to
write to the object.  Only priviledged methods can write to the object.
Now, any method can make itself priviledged if it wants to via a
dynamic_cast.  There's no way to stop it.  But, this has to be done
explicitly.  Just because something creates an object doesn't mean that
something has the right to change the object.

Does this make sense?


--- David


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Gerhard Menzl <gerhard.menzl@sea.ericsson.se>
Date: 2000/06/06
Raw View
Robert Spatschek wrote:

> Nevertheless I'm still curious about an explanation WHY this kind of
> inheritance is allowed in the standard. Which sense does it make NOT
> to forbid the transition from public (in A) to private (in B) if it is
> still possible to call the (now private) function from a non-member
> function g()? It is not compatible with my personal understanding of
> encapsulation. As you explained in a very clearly there is no
> possibility to detect this during compilation because the virtual
> function call is resolved at run time. Thus the only place where the
> contradictions (?) can be found is in the definition of the class B.
>
> Actually I guess that I simply overlook something. There must be a
> good reason why this constructions makes sense though, but I can't see
> it.

We have been discussing this in comp.lang.c++.moderated recently. The
following is a copy of a posting of mine that explains ...

.... why the orthogonality of access level and overriding
can be a valuable design tool (I think I have posted something similar a
few weeks ago; my apologies for the repetition). Suppose you want to
issue a beep whenever a certain event, say, a remote computer connecting
to TCP port 80, occurs (don't do this to your Web server at home!). A
typical object-oriented solution looks like this:

   +--------------+            +----------------------------------+
   | EventHandler |  notifies  |           Dispatcher             |
   +--------------+ <--------- +----------------------------------+
   | OnConnect () |            | Subscribe (EventHandler, PortNo) |
   +--------------+            +----------------------------------+
            ^                                  ^
           / \                                 |
           ---                                 |
            |                                  |
            |                                  |
   +--------------+                            |
   |   MyBeeper   |       subscribes           |
   +--------------+ ---------------------------+
   | Start ()     |
   | OnConnect () |
   +--------------+

How are we going to implement this in C++? Dispatcher is trivial, so I'm
not going to show it. EventHandler is a typical case of an abstract base
class:

   class EventHandler
   {
   public:
      virtual void OnConnect () = 0;
   };

OnConnect() is the interface towards Dispatcher, and it must be public,
otherwise the dispatcher cannot call it. Now for the concrete MyBeeper,
which implements OnConnect() by issuing a beep. Start() is the interface
towards its clients (the classes that instantiate and own it), so it has
to be public. But what about OnConnect(), the overridden version of the
pure virtual base class function? If we make it public, clients could
call it and generate fake beeps. Clearly, we do not want that. Only the
dispatcher can tell when a connect at port 80 takes place, so it should
be the only one allowed to call OnConnect(). Thanks to the clean
separation of virtual function dispatch and access level, this is very
easy in C++:

   class MyBeeper : private EventHandler
   {
   public:
      void Start () { globalDispatcher.Subscribe (this, 80); }

   private:
      virtual void OnConnect () { Beep (); }
   };

This yields two entirely separate interfaces that are both implemented
by MyBeeper: a public one that allows any client to start the beeper,
and a hidden one that is open only to the dispatcher. You get a maximum
of encapsulation and a clean separation of concerns. No friends
required. Note that private inheritance prevents clients of MyBeeper
even from calling OnConnect() via a base class pointer. MyBeeper decides
itself who gets to see the hidden back door.

Something that we should keep in mind but is often forgotten is that the
access level of a member function only tells you who has the right to
*call* it. It does not affect virtual function dispatch, and, as has
been shown, for a good reason. Think of ordinary member function calls
and virtual function dispatch as two entirely different pipes. Virtual
functions are like extra plug-ins that bypass the ordinary openings.

Some people even argue that virtual functions should always be private
and be called from non-virtual public functions. I would say this
depends on how much decoupling you need and are willing to pay for. I
see the merits of both approaches, and I don't see why either should be
considered bad style.

Gerhard Menzl

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Ron Natalie <ron@sensor.com>
Date: 2000/06/01
Raw View

Robert Spatschek wrote:
>
>   g(b);    // ok, but do I really want this??
> }

I'm confused, this ought not to compile.  You can't pass a B to something
that wants an A&.  Did you wish to make B derived from A?

> In A the virtual member function A::f() is public, but in the daughter
> class B it is private (this was my mistake). I was quite surprised that
> the program works!

I guess B is supposed to be derived from A.  You should not be surprised.
You need to understand how functions are called in C++.

First, the NAME is looked up in the appropriate scope (in this case A::f()
  is found).
Then possible overloads for that name are found and the best selected (only one
  in this case A::f(void)).
Then access permissions are checked (fine, a::f(void) is public).
Then if the function selected is virtual, look for a final overrider and call it
   (in this case B::f() is run).

Overload resolution, access checking, and default arguments all happen based on
the static type (A in this instance).  The virtual function is switched in at the
last minute.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Robert Spatschek <spatschk@physik.rwth-aachen.de>
Date: 2000/06/01
Raw View
First of all, thank you very much for your answer.

Ron Natalie wrote:

> I'm confused, this ought not to compile.  You can't pass a B to something
> that wants an A&.  Did you wish to make B derived from A?
>
Sorry, you are perfectly right. Of course B should be derived from A,
this was a simple typing error. (See the corrected version at the end of
this mail.)

> I guess B is supposed to be derived from A.  You should not be surprised.
> You need to understand how functions are called in C++.
>
> First, the NAME is looked up in the appropriate scope (in this case A::f()
>   is found).
> Then possible overloads for that name are found and the best selected (only one
>   in this case A::f(void)).
> Then access permissions are checked (fine, a::f(void) is public).
> Then if the function selected is virtual, look for a final overrider and call it
>    (in this case B::f() is run).
>
> Overload resolution, access checking, and default arguments all happen based on
> the static type (A in this instance).  The virtual function is switched in at the
> last minute.
>
That's fine, I agree with you. From point of view of type-checking
everything is correct, B also has an entry in its virtual function table
for the (overloaded) function B::f() which is called in g(b) since the
latter function expects a reference as parameter (I defined g as g(A&)
and not as g(A).)

Nevertheless I'm still curious about an explanation WHY this kind of
inheritance is allowed in the standard. Which sense does it make NOT to
forbid the transition from public (in A) to private (in B) if it is
still possible to call the (now private) function from a non-member
function g()? It is not compatible with my personal understanding of
encapsulation. As you explained in a very clearly there is no
possibility to detect this during compilation because the virtual
function call is resolved at run time. Thus the only place where the
contradictions (?) can be found is in the definition of the class B.

Actually I guess that I simply overlook something. There must be a good
reason why this constructions makes sense though, but I can't see it. I
hope that somebody can help me.

Probably I expressed my concerns not very precisely in the first mail, I
hope it's clearer now.


I better repeat the corrected sample code here:

class A
{
public:
  virtual void f() {}
};

class B : public A
{
private:
  virtual void f() {}
};

void g(A& a)
{
  a.f();
}

int main()
{
  A a;
  B b;

  a.f();   // ok
  // b.f();   // forbidden, B::f() is private
  g(a);    // ok
  g(b);    // ok, but do I really want this??
}

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Robert Klemme <robert.klemme@myview.de>
Date: 2000/06/01
Raw View
i think this is more a question for clcm.  there was a recent discussion
on this topic a while ago.  look for subject "Overriding and access
level" in this comp.lang.c++.moderated (or deja news).

regards

 robert

--
Robert Klemme
Software Engineer
-------------------------------------------------------------
myview technologies GmbH & Co. KG
Riemekestra   e 160 ~ D-33106 Paderborn ~ Germany
E-Mail: mailto:robert.klemme@myview.de
Telefon: +49/5251/69090-321 ~ Fax: +49/5251/69090-399
-------------------------------------------------------------
---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Robert Spatschek <spatschk@physik.rwth-aachen.de>
Date: 2000/05/31
Raw View
By accident I wrote a program which was similar to this fragment:

class A
{
public:
  virtual void f() {}
};

class B
{
private:
  virtual void f() {}
};

void g(A& a)
{
  a.f();
}

int main()
{
  A a;
  B b;

  a.f();   // ok
  // b.f();   // forbidden, B::f() is private
  g(a);    // ok
  g(b);    // ok, but do I really want this??
}

In A the virtual member function A::f() is public, but in the daughter
class B it is private (this was my mistake). I was quite surprised that
the program works! At first I expected (without thinking) that some
trouble would occur during the call g(b), because this function gets
access to the private method B::f().
Of course the compiler cannot foresee my stupidity when he translates
the function g(A) or the call g(b) in main(). Nevertheless I am a bit
alarmed that I did not even got a compiler warning that the change from
public to private might cause problems. This doesn't seem to be a
compiler-specific behaviour, I tried it with several of them and always
received the same result.
My question is therefore whether this kind of programming is provided in
the C++ standard and if so, what is it good for. Is it allowed to change
the scope between public, private and protected? I looked through
different books but couldn't find a hint (unfortunately I don't have the
standard document).
If I conversely use real private inheritance
class B : private A {...};
everything is fine and the compiler rejects the call g(b).

Sorry that I'm not an expert and bore you with my trivial questions,

Robert Spatschek.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]