Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

virtual method hiding

9 views
Skip to first unread message

Vlad Simionescu

unread,
Mar 9, 2004, 4:05:36 AM3/9/04
to
Hello

I'll bring up a topic that is probably old to many - virtual methods
defined in a derived class hide those from the base class, regardless
if it's a method that redefines another one or a completely new one
(with a new signature, that is).
I know that happens, I know that it's supposed to happen like this,
what I don't really understand is why it's like this.
I've searched for explanations for a while, and the best I could find
is that if for instance if I have

virtual void F (void* p);

in the base class, and

virtual void F (long* p);

in the derived class, then in the code sequence

long* pLong = NULL;
Derived d;
d.F (pLong);

Base& b = d;
b.F (pLong);

the call to b::F will result in a different treatment for pLong than
the call for d::F; which violates the principle of polymorphism, which
states that a virtual method call should mean the same thing in the
base object as in the derived one.

OK until here, I can understand this and I agree. But the solution,
hiding all base methods with the same name, is IMO too radical, on the
one side, and also not very suited to solve the problem it wants to
solve (if, again, that's the main reason for this hiding behaviour).

To try to remain short, it is too radical because:
1. It happens like this even if the only method added in the derived
class is a redefinition of one of the base class.
2. It happens like this even if the only method added in the derived
class is a new one of completely different argument types, that have
nothing in common with those of the methods in the base class.
3. It happens like this even if the base class is protected.
In all these situations, allowing the other base methods as well
clearly dosen't violate the principle of polymorphism. In cases 1 and
3 the compiler can tell this for sure.

Needless to say, all 3 situations above appear often in practice, and
in either one of them the hiding behaviour can be very cumbersome. I
know there are workarounds, but as far as I know they are not very
nice, and anyway they complicate the code.

Hiding the base class methods is not very suited as a solution for the
"polymorphism violation" problem, because even like this, polymorphism
is still violated. One could only argue that it does make live harder
for the programmer, so he'll think twice before adding derived methods
that could harm polymorphism. Indeed he'll have to think twice - even
before legitimately redefining any virtual method, if it happens to be
overloaded in the base class. This part I find relly strange, since
virtual methods are here to be redefined. I wasn't really aware of it
until it struck me recently.

IMO, the reasonable thing to do would be to allow the base class
methods anyway, and issue a warning in the situations where a
violation of the polym. principle might be possible. That is, when the
compiler cannot say for sure that it's not possible. After all, it's
the programmer's decision and responsibility.

Please comment. I would especially like other opinions on the real
reason for hiding base methods, on its utility (that is if you like it
or, like myself, hate it) and on the best workarounds.

Thanks,
V. Simionescu

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Pete Vidler

unread,
Mar 9, 2004, 1:13:26 PM3/9/04
to
Vlad Simionescu wrote:
[snip]

> I'll bring up a topic that is probably old to many - virtual methods
> defined in a derived class hide those from the base class, regardless
> if it's a method that redefines another one or a completely new one
> (with a new signature, that is).
[snip]

First of all, it's not just virtual methods that get hidden. C++ does
not allow *any* overloading across scope boundaries (straight from
Stroustrup: http://tinyurl.com/3yqz2), except where you explicitly
request it (with a using declaration).

> I know that happens, I know that it's supposed to happen like this,
> what I don't really understand is why it's like this.

The situation outlined on the webpage I gave earlier is a good example.
Suppose you had a large class hierarchy, which was not entirely created
by you (MFC or ATL, say). If you derive from a class in that hierarchy
(and overloading across different scopes was allowed for derived
classes) then you could quite easily cause a runtime error by accident.

If a base class in the hierarchy had a method "MyFunc( int a )" (that
you didn't notice, as it wasn't written by you) and your derived class
has "MyFunc( double a )", then you could hit problems. Suppose you try
to call "MyFunc( 5 )", expecting to get an implicit conversion to
double? You wouldn't.. instead you would be calling the base class
method, which might do anything (i.e. an unpredictable, hard to debug,
runtime error).

That example is too contrived, but C++ is protecting you from such
subtle mistakes. A little extra typing (the using declaration) is a
small price to pay.

[snip]


> To try to remain short, it is too radical because:
> 1. It happens like this even if the only method added in the derived
> class is a redefinition of one of the base class.

I'm not sure what you mean by redefinition? If the virtual method in the
derived class has the same signiture, it won't hide the base method (it
will override it)?

> 2. It happens like this even if the only method added in the derived
> class is a new one of completely different argument types, that have
> nothing in common with those of the methods in the base class.

Which protects you from the subtle mistakes I mentioned earlier. Not
real problems if you write all your own code, but really nasty if you're
working with an existing hierarchy.

> 3. It happens like this even if the base class is protected.
> In all these situations, allowing the other base methods as well
> clearly dosen't violate the principle of polymorphism. In cases 1 and
> 3 the compiler can tell this for sure.

Again, it's not just virtual methods that show this behaviour. Violating
the principles of polymorphism isn't the only problem.

> Hiding the base class methods is not very suited as a solution for the
> "polymorphism violation" problem, because even like this, polymorphism
> is still violated. One could only argue that it does make live harder
> for the programmer, so he'll think twice before adding derived methods
> that could harm polymorphism. Indeed he'll have to think twice - even
> before legitimately redefining any virtual method, if it happens to be
> overloaded in the base class. This part I find relly strange, since
> virtual methods are here to be redefined. I wasn't really aware of it
> until it struck me recently.

This is not a bad thing. Programmers *should* think twice when
overriding a virtual method. Methods are usually marked virtual to meet
specific usage scenarios... the hierarchy might not be tested for many
uses outside these scenarios, so you should think twice about any
overrides you write.

> IMO, the reasonable thing to do would be to allow the base class
> methods anyway, and issue a warning in the situations where a
> violation of the polym. principle might be possible. That is, when the
> compiler cannot say for sure that it's not possible. After all, it's
> the programmer's decision and responsibility.

Why issue a warning when it could so easily be an error? Is a simple
using declaration too much typing?

> Please comment. I would especially like other opinions on the real
> reason for hiding base methods, on its utility (that is if you like it
> or, like myself, hate it) and on the best workarounds.

[snip]

I like it, it's saved me from a few mistakes that would've had me
tearing my hair out if the compiler hadn't caught them.

-- Pete

ka...@gabi-soft.fr

unread,
Mar 10, 2004, 4:35:38 AM3/10/04
to
vsimi...@softwin.ro (Vlad Simionescu) wrote in message
news:<db6c3632.0403...@posting.google.com>...

> I'll bring up a topic that is probably old to many - virtual methods
> defined in a derived class hide those from the base class, regardless
> if it's a method that redefines another one or a completely new one
> (with a new signature, that is). I know that happens, I know that
> it's supposed to happen like this, what I don't really understand is
> why it's like this. I've searched for explanations for a while, and
> the best I could find is that if for instance if I have

> virtual void F (void* p);

> in the base class, and

> virtual void F (long* p);

> in the derived class, then in the code sequence

> long* pLong = NULL;
> Derived d;
> d.F (pLong);

> Base& b = d;
> b.F (pLong);

> the call to b::F will result in a different treatment for pLong than
> the call for d::F; which violates the principle of polymorphism, which
> states that a virtual method call should mean the same thing in the
> base object as in the derived one.

The problem has nothing to do with polymorphism, since the solution
affects both virtual and non-virtual functions. The problem involves
overload resolution:

struct Base { void f( char ) ; } ;
struct Derived : Base { void f( int ) ; void g() ; } ;

void
Derived::g()
{
f( 'a' ) ; // Calls Derived::f( int ) ...
}

The general feeling was, I believe, that having the base class functions
participate in overload resolution here was too fragile. Suppose, for
example, that Base didn't initially contain f -- the resolution is
trivial, it's what the programmer expects, and his program works.
Later, the function f is added to Base. Suddenly, the overload
resolution in Derived::g() selects a completely different function, and
(probably) the code breaks. There is no reason that adding a new,
unrelated function to the Base class should break the derived class,
just because it happens to have the same name as the function in the
derived class. Such things are largely implementation details, and
should be invisible in the derived class.

In the case of virtual functions, the argument is considerably weaker --
*all* virtual functions (even private ones) are part of the protected
interface, in the sense that derived classes "know" about them. Adding
a virtual function in the base class *does* require examining all of the
derived classes, if only to ensure that no derived class accidentally
overrides the new function with some completely unrelated, previously
purely internal function that just happens to have the same name.

Thus, one could argue that virtual functions shouldn't be hidden. On
the other hand, I definitly wouldn't like the idea that the virtuality
of a function affects name lookup and overload resolution; overload
resolution is complicated enough as it is. I find the current solution,
in which I can easily use a using declaration in the derived class to
bring in all of the functions with the same name, a very good
compromise: it makes it very easy to solve the problem, when it is a
problem, while not adding additional complexity to name lookup and
overload resolution.

[...]


> Please comment. I would especially like other opinions on the real
> reason for hiding base methods, on its utility (that is if you like it
> or, like myself, hate it) and on the best workarounds.

I would say that it is absolutely imperative in the case of non-virtual
functions, at least in the context of the C++ protection model[1]. For
virtual functions, the arguments in favor of name hiding are
considerably weaker, but having name lookup and overload resolution
depend on whether the function was virtual or not seems far too subtle
to me. All in all, I rather like the current situation. For that
matter, I didn't have any problem with the situation before using -- it
sometimes meant a bit of additional typing, but rarely enough to get
upset about. And it was considerably less fragile than the situation in
Java.

[1] Java doesn't use name hiding in general, but names declared private
in the base class are not visible at all in the derived class --
they are effectively hidden whether the derived class redefines them
or not. This avoids some of the problems, but it also renders some
very useful idioms, like programming by contract, very difficult, if
not impossible.)

--
James Kanze GABI Software mailto:ka...@gabi-soft.fr
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16

Vlad Simionescu

unread,
Mar 11, 2004, 7:13:34 AM3/11/04
to
Hello

First of all, thanks for your comments.

I'll reply in a mixed manner to both your messages.
It's probably true that overloading a base class method with a derived
one can cause problems, but this sort of problems are expected when
overloading functions, and generally when working in C++. It could
happen inside the same class (even in one's own class - it has
happened to me). I agree it's much more likely to happen across
hierarchies, but one should be expected to find such problems without
much difficulty, I guess. Plus it can happen also with global
functions, if you use headers you don't know what they contain (and
nobody knows everything there is in all headers he uses). So, I find
that this problem is a general one and I don't like this solution very
much.

The situation with the function that is added to the base class (in a
new version) is indeed a problem, though again it can occur also with
versions of headers / libraries that contain gobal functions. I'm not
sure this is how it should be solved. It's the first time I hear about
this, so I may be wrong or miss some points, but I would think that by
the same line of reasoning one should never add new public operators
in new versions of classes. Or else, somebody may have defined the
operator, with a related argument, as a global function. To bring this
ad absurdum, one could never add new global functions in new versions
of headers. And, because of the "using" directive, he shouldn't add
public or protected methods in classes either, or else some methods
called via using could cause troubles.

I don't know, maybe the best thing to do would be to let classes as
they are, that is never add public / protected methods, just change
their definition if necessary. Or get to the next level and work (and
design) with interfaces. Anyway, I don't like it when forwards
compatibility considerations affect the way a class is designed from
scratch.

But, after all, it seems the using directive is indeed a simple enough
solution. Except that it brings up the same problems.

0 new messages