Why shouldn't virtual functions be overused?

I just read that we should not overuse the virtual function. People felt that less virtual features had fewer errors and reduced maintenance.

What bugs and flaws can occur due to virtual functions?

I am interested in the context of C ++ or Java.




One of the reasons I can think of is because of a virtual function, which can be slower than regular functions due to v-table lookups.

+11
java c ++ design virtual-functions
Jun 16 2018-10-06T00:
source share
9 answers

You have published some general statements that, as I think, most pragmatic programmers will dismiss misinformation or misinterpretation. But there are anti-virtual fanatics, and their code can be just as bad for performance and maintenance.

In Java, everything is virtual by default. Saying you shouldn't overuse virtual functions is pretty strong.

In C ++, you should declare a function virtual, but it is perfectly acceptable to use it when necessary.

I just read that we should not overuse the virtual function.

It is difficult to define "overly" ... of course, "use virtual functions when appropriate" is good advice.

People felt that fewer virtual features had fewer errors and reduced service levels. I can not understand what errors and shortcomings may arise due to virtual functions.

Poorly designed code is hard to maintain. Period.

If you are a library curator while debugging code buried in a high-class hierarchy, it can be difficult to track where the code actually runs without using the powerful IDE, it is often hard to tell which class overrides the behavior. This can lead to a big jump between files tracking inheritance trees.

So there are some rules of thumb, all with exceptions:

  • Keep the hierarchy shallow. Tall trees create tangled classes.
  • In C ++, if your class has virtual functions, use a virtual destructor (if not, this is probably an error)
  • As with any hierarchy, maintain an is-a relationship between derived and base classes.
  • You should know that a virtual function cannot be called at all ... so do not add implicit expectations.
  • There it is difficult to argue that virtual functions are slower. It is dynamically connected, so it often happens. Whether it matters in most cases, that led him, of course, is debatable. Profile and optimize instead :)
  • In C ++, do not use virtual when it is not needed. There, the semantic meaning is to label the virtual function - do not abuse it. Let the reader know that “yes, it can be redefined!”.
  • Prefer clean virtual interfaces to a hierarchy that mixes the implementation. It is cleaner and much easier to understand.

The reality of the situation is that virtual functions are incredibly useful, and these shades of doubt are unlikely to come from balanced sources - virtual functions have been widely used for a very long time. Newer languages ​​accept them by default than otherwise.

+14
Jun 16 '10 at 5:30
source share

Virtual functions are slightly slower than regular functions. But this difference is so small that it does not affect everything except the most extreme circumstances.

I think the best reason to prevent virtual functions is to protect against misuse of the interface.

It is a good idea to write classes that will be open for extension, but there is such a thing as being too open. By carefully planning which functions are virtual, you can control (and protect) how the class extends.

Errors and maintenance problems occur when a class expands, so that it violates the base class contract. Here is an example:

class Widget { private WidgetThing _thing; public virtual void Initialize() { _thing = new WidgetThing(); } } class DoubleWidget : Widget { private WidgetThing _double; public override void Initialize() { // Whoops! Forgot to call base.Initalize() _double = new WidgetThing(); } } 

Here, DoubleWidget broke the parent class because Widget._thing is null. There's a pretty standard way to fix this:

 class Widget { private WidgetThing _thing; public void Initialize() { _thing = new WidgetThing(); OnInitialize(); } protected virtual void OnInitialize() { } } class DoubleWidget : Widget { private WidgetThing _double; protected override void OnInitialize() { _double = new WidgetThing(); } } 

Now the widget will not be fired at a NullReferenceException later.

+7
Jun 16 '10 at 5:41
source share

Each dependency increases the complexity of the code and makes it difficult to maintain. When you define your function as virtual, you create a dependency of your class on some other code that may not even exist at the moment.

For example, in C, you can easily find what foo () does - there is only one foo () there. In C ++, without virtual functions, this is a little more complicated: you need to examine your class and its base classes to find which foo () we need. But at least you can do it deterministically in advance, rather than at run time. With virtual functions, we cannot determine which foo () is executed, since it can be defined in one subclass.

(Another issue is the performance issue you were talking about because of the v-table).

+6
Jun 16 '10 at 5:25
source share

I suspect that you misunderstood this expression.

An overly subjective term, I think that in this case it meant “when you don't need it”, and not that you should avoid it when it might be useful.

In my experience, some students, when they learn about virtual functions and are burned for the first time, forgetting to make the function virtual, think that it is reasonable to simply make each function virtual .

Since virtual functions incur costs for each method call (which in C ++ usually cannot be excluded due to separate compilation), you are essentially paying now for each method call, as well as for preventing inline. Many teachers prevent students from doing this, although the term "excessive" is a very poor choice.

Java uses “virtual” behavior (dynamic dispatching) by default. However, the JVM can optimize the situation on the fly and theoretically can eliminate some virtual challenges when the target identification is clear. In addition, final methods or methods in final classes can often be allowed for the same purpose at compile time.

+3
Jun 16 '10 at 16:11
source share

In C ++: -

  • Virtual functions have a slight performance limitation. Usually it is too small to make any difference, but in a tight cycle it can be significant.

  • A virtual function increases the size of each object by one pointer. Again, this is usually negligible, but if you create millions of small objects, this can be a factor.

  • Classes with virtual functions are usually inherited. Derived classes can replace some, all, or none of the virtual functions. This can create additional complexity and complexity - these are programmers of a mortal enemy. For example, a derived class may poorly implement a virtual function. This can break part of the base class that relies on a virtual function.

Now let me be clear: I do not say "do not use virtual functions". They are an important and important part of C ++. Just keep in mind the potential for complexity.

+2
Jun 16 '10 at 5:33
source share

We recently had a great example of how misuse of virtual functions introduces errors.

There is a shared library that has a message handler:

 class CMessageHandler { public: virtual void OnException( std::exception& e ); ///other irrelevant stuff }; 

it is assumed that you can inherit this class and use it for custom error handling:

 class YourMessageHandler : public CMessageHandler { public: virtual void OnException( std::exception& e ) { //custom reaction here } }; 

The error handling mechanism uses the CMessageHandler* pointer, so it does not need the actual type of the object. The function is virtual, so whenever there is an overloaded version, the last one is called.

Cool huh Yes, that was until the developers of the shared library changed the base class:

 class CMessageHandler { public: virtual void OnException( const std::exception& e ); //<-- notice const here ///other irrelevant stuff }; 

... and the overloads just stopped working.

Do you see what happened? After changing the base class, overloads ceased to be overloads from the point of view of C ++ - they became new, other, unrelated functions.

The base class had a default implementation that was not marked as pure virtual, so derived classes were not forced to overload the default implementation. And finally, functon is called only in case of error handling, which is not used every time here and there. Thus, the error was introduced silently and for a long time went unnoticed.

The only way to eliminate it once and for all is to perform a search on the entire code base and edit all the relevant code fragments.

+2
Jun 16 2018-10-06T00:
source share

I don't know where you are reading this, but imho is not about performance at all.

Maybe its more about “preferring composition about inheritance” and about problems that might arise if your classes / methods are not final (mostly java) but not meant to be reused. There are many things that can go really wrong:

  • Perhaps you use virtual methods in your constructor - after they are canceled, your base class calls an overridden method that can use resources. Ressources is initialized in the constructor subclass - which starts later (NPE goes up).

  • Imagine the add and addAll method in a list class. addAll calls add many times and both are virtual. Someone can override them to count how many elements have been added to all. Unless you document that addAll calls add, the developer can (and will) override both add and addAll (and add some counter ++ stuff to them). But now, if youse addAll, each element is counted twice (add and addAll), which leads to incorrect results and it is difficult to find errors.

To summarize, if you are not designing your class for extension (providing hooks, documenting some important implementation things), you should not inherit at all, because this can lead to errors. It is also easy to remove the final modifier (and possibly redesign it for reuse) from one of your classes if necessary, but it cannot be made the final class (where subclasses lead to errors) permanently, because others may already subclass.

Perhaps it was really about performance, and then, at least from the topic. But if this is not the case, you have good reason not to extend your classes if you really don't need it.

Additional information about such materials in fleas Effective Java (this particular post was written a few days after I read paragraph 16 (“I prefer composition over inheritance”) and 17 (“design and document for inheritance or prohibition on it”) - amazing book.

+1
Jun 16 '10 at 6:17
source share

I worked sporadically as a consultant on the same C ++ system for about 7 years, checking the work of about 4-5 programmers. Every time I returned, the system became worse and worse. At some point, someone decided to remove all the virtual functions and replace them with a very stupid factory / RTTI system, which essentially did everything that the virtual functions did, but worse, more expensive, thousands of lines more code, a lot of work, a lot of testing, ... Completely and completely pointless, and clearly fear of the unknown.

They also wrote dozens of copy constructors with errors when the compiler automatically created them without errors, with three exceptions that required a handwritten version.

Moral: Do not fight with the tongue. It gives you things: use them.

0
Jun 17 '10 at 1:22
source share

A virtual table is created for each class, has virtual functions, or is obtained from a class containing virtual functions. This consumes more conventional space.

The compiler must silently embed additional code to ensure that the last binding occurs instead of early binding. It consumes more than usual time.

0
Nov 02 2018-11-11T00:
source share



All Articles