Artima.com has published Part III of an interview with Scott Meyers in which he discusses the importance of saying what you mean and understanding what you say, the three fundamental relationships between classes, and the difference between virtual and non-virtual functions.
As a human being, if I'm reading your code and I see that a class publicly inherits from that class, I will think, "This is an IS-A relationship." If that's not what you meant, I'll be confused. Or if I see a non-virtual member function, I will think, "This is an invariant. Nobody should ever override this." If I see it has been overridden in a derived class, I see a contradiction in your design. I no longer trust my understanding of your design, because I'm getting conflicting pieces of information. That makes it more difficult for me to understand what your program is supposed to do. So meaning is important because first, you don't want to ascribe semantics to a language feature that are different from what the compiler will enforce. And second, you don't want to miscommunicate what you're trying to express to other people who read your code.
"Regarding the C# decision that the default should be non-virtual rather than virtual, I can't put words in the designer's mind. I can tell you exactly why it's the default in C++."
I certainly can't put words in the C# designer's mind. But I do believe this feature is an improvement over Java.
The reason for this is that it is very difficult to design a class for extension. What is safe for a subclasser to override? If a method in a class wasn't designed to be overridden, then there is a good chance that there will be undesirable behavior. See Joshua Bloch's item #15 in Effective Java.
I remember hearing that the original implementation of java.util.Hashtable allowed overriding of put(key, value), and there was some vague complication because it was never designed to be overridden. For example, you might try to override it by saying:
if (!somecondition) super.put(key, value);
However, the original java.util.Hashtable implementation did something like this pseudocode:
if (get existing entry) update existing entry and return the old value if (adding) { increment count if (count > threshold) { rehash() put(key, value) } } create new entry
Notice that this would result in "put" on the inheriting class being called twice on occasion of needing to rehash (ie, unpredictably from the using application's point of view).
Scott is great. I use to work at Mentor Graphics and he would often consult with our developers. Very insightful and his STL course is a must for anyone using the STL...
That said, what do either Scott our yourself think about function overriding. That is it seems to break the IS-A paradigm yet is widely taught in university (including using the :: operator to access the base class implementation)...