Name Hiding and Fragile Base Problem

Can someone give a better example of fragile base class issues?

Yes - java.util.Properties is a pain to derive from.

For the moment let's leave aside the fact that it derives from java.util.Hashtable to start with (which means it has get(Object) and put(Object, Object) despite the fact that properties are always strings...

I once subclassed java.util.Properties to provide a sort of hierarchical structure - in a properties file I had:

x.y.z = 10
a.b.c = 10

and you could ask the "master" properties for "x" (with a new method call) and it would give you another properties object which would effectively contain "y.z = 10" etc. It was handy to subclass java.util.Properties as many other pieces of code already knew used properties. If it had implemented an interface, I wouldn't have subclassed, and I wouldn't have had any problems :(

Anyway, I needed to override getProperty() to refer back to the parent properties if necessary. But there are two overloads - which should I override? Does getProperty(String) call getProperty(String,String) or vice versa? Maybe I only need to override get()? It's not documented, and if I only overrode one of them, the implementation could change in a later version to switch things round - so I needed to override both of them.

The same went for various other methods (saving and loading were a pain, IIRC - this was a long time ago). Basically I could have done the job more simply if I could have relied on bits of the implementation of Properties not changing - but that would obviously have made it harder for Sun to improve the implementation at a later date.

Anyway, this was a definite example where composition and exposing an interface which clients would rely on would have been much better.

What is the fragile base class problem?

A fragile base class is a common problem with inheritance, which applies to Java and any other language which supports inheritance.

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. You can prevent other classes inheriting from a class by labelling the class declaration as final in Java.

A best practice to avoid the worst of these problems is to label all classes as final unless you are specifically intending to inherit from them. For those to intend to inherit from, design them as if you were designing an API: hide all the implementation details; be strict about what you emit and careful about what you accept, and document the expected behaviour of the class in detail.

How to avoid the fragile base class in Java

However, most of the material that I have found about the FBC problem dates back to the late 90s, early 00s, so it makes me wonder if the 'problem' is no longer a major issue.

I think it's more that the issue is now well-understood. Similarly, you won't find too many recent papers discussing problems with GOTO and how to address them, not because these problems no longer exist, but because people now know how to avoid them.

Is [the proposed class sealing mechanism] not basically the same thing as making classes default/package-private?

No. Package-private classes and "sealed" classes are similar in that both cannot be extended by classes outside the package, but they differ in that the former also cannot be used by classes outside the package. That is — if a class X is package-private, then a class outside its package can't even refer to X: no extends X, no X x = new X(), no Class<X> clazz = X.class. But if it's merely sealed, then a class in a different package cannot write extends X, but can still write X x = new X() and Class<X> clazz = X.class and so on. (Just as important, it can still write X x = new Y(), if Y is a subclass. So it can still take advantage of X's type hierarchy, even though it itself can't extend X.)

Reason for C++ member function hiding

Name lookup works by looking in the current scope for matching names, if nothing is found then it looks in the enclosing scope, if nothing is found it looks in the enclosing scope, etc. until reaching the global namespace.

This isn't specific to classes, you get exactly the same name hiding here:

#include <iostream>

namespace outer
{
void foo(char c) { std::cout << "outer\n"; }

namespace inner
{
void foo(int i) { std::cout << "inner\n"; }

void bar() { foo('c'); }
}
}

int main()
{
outer::inner::bar();
}

Although outer::foo(char) is a better match for the call foo('c') name lookup stops after finding outer::inner::foo(int) (i.e. outer::foo(char) is hidden) and so the program prints inner.

If member function name weren't hidden that would mean name lookup in class scope behaved differently to non-class scope, which would be inconsistent and confusing, and make C++ even harder to learn.

So there's no technical reason the name lookup rules couldn't be changed, but they'd have to be changed for member functions and other types of name lookup, it would make compilers slower because they'd have to continue searching for names even after finding matching names in the current scope. Sensibly, if there's a name in the current scope it's probably the one you wanted. A call in a scope A probably wants to find names in that scope, e.g. if two functions are in the same namespace they're probably related (part of the same module or library) and so if one uses the name of the other it probably means to call the one in the same scope. If that's not what you want then use explicit qualification or a using declaration to tell the compiler the other name should be visible in that scope.

Why is a property hiding Inherited function with same name?

Section 10.3.3 of the C# spec contains:

A derived class can hide inherited members by declaring new members with the same name or signature.

It doesn't say anything about the two members having to be the same kind of member. Note that even though one is a property and one is a method, there are still cases where they could be confused:

  • The property could be of a delegate type, e.g. Action<int, int> in which case PropertyName(10, 10) would be valid.
  • In some places where you use the property name, you could be trying to perform a method group conversion, so they'd both be valid.

Within the same class, we run into the rules of section 10.3:

  • The names of constants, fields, properties, events, or types must differ from the names of all other members declared in the same class.
  • The name of a method must differ from the names of all other nonmethods declared in the same class. [...]

The fact that you can't declare a method called get_myName is mentioned in section 10.3.9.1, as noted in comments.

Overriding functions in abstract base class

Put using base::toBeCalled; in class derived.

I'm pretty sure this is a dupe, but I can't find one, so I won't go into detail. But when you override (or overload) a function in a derived class you hide all base class versions of that function.

Rationale for the behavior is discussed here: https://stackoverflow.com/a/12036004/13005



Related Topics



Leave a reply



Submit