name hiding and fragile base problem

Solution 1:

I'll assume that by "fragile base class", you mean a situation where changes to the base class can break code that uses derived classes (that being the definition I found on Wikipedia). I'm not sure what virtual functions have to do with this, but I can explain how hiding helps avoid this problem. Consider the following:

struct A {};

struct B : public A
{
    void f(float);
};

void do_stuff()
{
    B b;
    b.f(3);
}

The function call in do_stuff calls B::f(float).

Now suppose someone modifies the base class, and adds a function void f(int);. Without hiding, this would be a better match for the function argument in main; you've either changed the behaviour of do_stuff (if the new function is public), or caused a compile error (if it's private), without changing either do_stuff or any of its direct dependencies. With hiding, you haven't changed the behaviour, and such breakage is only possible if you explicitly disable hiding with a using declaration.

Solution 2:

I don't think that overloads of virtual functions are treated any differently that overloads of regular functions. There might be one side effect though.

Suppose we have a 3 layers hierarchy:

struct Base {};

struct Derived: Base { void foo(int i); };

struct Top: Derived { void foo(int i); }; // hides Derived::foo

When I write:

void bar(Derived& d) { d.foo(3); }

the call is statically resolved to Derived::foo, whatever the true (runtime) type that d may have.

However, if I then introduce virtual void foo(int i); in Base, then everything changes. Suddenly Derived::foo and Top::foo become overrides, instead of mere overload that hid the name in their respective base class.

This means that d.foo(3); is now resolved statically not directly to a method call, but to a virtual dispatch.

Therefore Top top; bar(top) will call Top::foo (via virtual dispatch), where it previously called Derived::foo.

It might not be desirable. It could be fixed by explicitly qualifying the call d.Derived::foo(3);, but it sure is an unfortunate side effect.

Of course, it is primarily a design problem. It will only happen if the signature are compatible, else we'll have name hiding, and no override; therefore one could argue that having "potential" overrides for non-virtual functions is inviting troubles anyway (dunno if any warning exist for this, it could warrant one, to prevent being put in such a situation).

Note: if we remove Top, then it is perfectly fine to introduce the new virtual method, since all old calls were already handled by Derived::foo anyway, and thus only new code may be impacted

It is something to keep in mind though when introducing new virtual methods in a base class, especially when the impacted code is unknown (libraries delivered to clients).

Note that C++0x has the override attribute to check that a method is truly an override of a base virtual; while it does not solve the immediate problem, in the future we might imagine compilers having a warning for "accidental" overrides (ie, overrides not marked as such) in which case such an issue could be caught at compile-time after the introduction of the virtual method.