I've seen it stated that C++ has name hiding for the purposes of reducing the fragile base class problem. However, I definitely don't see how this helps. If the base class introduces a function or overload that previously did not exist, it might conflict with those introduced by the derived class, or unqualified calls to global functions or member functions- but what I don't see is how this is different for overloads. Why should overloads of virtual functions be treated differently to, well, any other function?
Edit: Let me show you a little more what I'm talking about.
struct base {
virtual void foo();
virtual void foo(int);
virtual void bar();
virtual ~base();
};
struct derived : base {
virtual void foo();
};
int main() {
derived d;
d.foo(1); // Error- foo(int) is hidden
d.bar(); // Fine- calls base::bar()
}
Here, foo(int)
is treated differently to bar()
, because it's an overload.
The fragile base class problem is a fundamental architectural problem of object-oriented programming systems where base classes (superclasses) are considered "fragile" because seemingly safe modifications to a base class, when inherited by the derived classes, may cause the derived classes to malfunction.
In a nutshell, the base class is the class you are inheriting from, and it is often called fragile because changes to this class can have unexpected results in the classes that inherit from it. There are few methods of mitigating this; but no straightforward method to entirely avoid it while still using inheritance.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With