Why should virtual functions not be used excessively?
Asked Answered
M

10

15

I just read that we should not use virtual function excessively. People felt that less virtual functions tends to have fewer bugs and reduces maintenance.

What kind of bugs and disadvantages can appear due to virtual functions?

I'm interested in context of C++ or Java.


One reason I can think of is virtual function may be slower than normal functions due to v-table lookup.

Malang answered 16/6, 2010 at 4:57 Comment(2)
actually I'm also searching for the link. I read this few days ago and still thinking. One reason I can think of is virtual function may be slow than normal function due to v-table lookup. I'm referring C++/Java.Malang
It's worth noting that within a shared library, a virtual function call isn't necessarily any slower than a non-virtual function call, because a non-virtual function call will be called indirectly via a PLT entry.Paleo
P
15

You've posted some blanket statements that I would think most pragmatic programmers would shrug off as being misinformed or misinterpreted. But, there do exist anti-virtual zealots, and their code can be just as bad for performance and maintenance.

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

In C++, you must declare a function virtual, but it's perfectly acceptable to use them when appropriate.

I just read that we should not use virtual function excessively.

It's hard to define "excessively"... certainly "use virtual functions when appropriate" is good advice.

People felt that less virtual functions tends to have fewer bugs and reduces maintenance. I'm not able to get what kind of bugs and disadvantages can appear due to virtual functions.

Poorly designed code is hard to maintain. Period.

If you're a library maintainer, debugging code buried in a tall class hierarchy, it can be difficult to trace where code is actually being executed, without the benefit of a powerful IDE, it's often hard to tell just which class overrides the behavior. It can lead to a lot of jumping around between files tracing inheritance trees.

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

  • Keep your hierarchies shallow. Tall trees make for confusing classes.
  • In c++, if your class has virtual functions, use a virtual destructor (if not, it's probably a bug)
  • As with any hierarchy, keep to a 'is-a' relationship between derived and base classes.
  • You have to be aware, that a virtual function may not be called at all... so don't add implicit expectations.
  • There's a hard-to-argue case to be made that virtual functions are slower. It's dynamically bound, so it's often the case. Whether it matters in most of the cases that its cited is certainly debatable. Profile and optimize instead :)
  • In C++, don't use virtual when it's not needed. There's semantic meaning involved in marking a function virtual - don't abuse it. Let the reader know that "yes, this may be overridden!".
  • Prefer pure virtual interfaces to a hierarchy that mixes implementation. It's 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 coming from balanced sources - virtual functions have been widely used for a very long time. More newer languages are adopting them as the default than otherwise.

Pepito answered 16/6, 2010 at 5:30 Comment(0)
R
8

Virtual functions are slightly slower than regular functions. But that difference is so small as to not make a difference in all but the most extreme circumstances.

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

It's a good idea to write classes to be open for extension, but there's such a thing as too open. By carefully planning which functions are virtual, you can control (and protect) how a class can be extended.

The bugs and maintenance problems appear when a class is extended such that it breaks the contract of the base class. Here's 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 fairly 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 Widget won't run into a NullReferenceException later.

Reunion answered 16/6, 2010 at 5:41 Comment(2)
'Virtual functions are slightly slower than regular functions.' No they're not, because they do more. After you add the necessary if/else chain or switch statement around the non-virtual function so it does some dynamic despatching, the performance of the non-virtual function is probably worse.Jade
@EJP Such switch statements are easily optimized, as all the information is in one place.Damara
O
6

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

For example, in C, you can easily find what foo() does - there's just one foo(). In C++ without virtual functions, it's slightly more complicated: you need to explore your class and its base classes to find which foo() we need. But at least you can do it deterministically in advance, not in runtime. With virtual functions, we can't tell which foo() is executed, since it can be defined in one the subclasses.

(Another thing is the performance issue that you mentioned, due to v-table).

Owl answered 16/6, 2010 at 5:25 Comment(1)
I like this answer, but I think @Stephen's answer is better because it's more complete and specific. But it misses the really important dependency point you're making.Disguise
M
3

I suspect you misunderstood the statement.

Excessively is a very subjective term, I think that in this case it meant "when you don't need it", not that you should avoid it when it can be useful.

In my experience, some students, when they learn about virtual functions and get burned the first time by forgetting to make a function virtual, think that it is prudent to simply make every function virtual.

Since virtual functions do incur a cost on every method invocation (which in C++ cannot usually be avoided because of separate compilation), you are essentially paying now for every method call and also preventing inlining. Many instructors discourage students from doing this, though the term "excessive" is a very poor choice.

In Java, a "virtual" behavior (dynamic dispatching) is the default. However, The JVM can optimize things on the fly, and could theoretically eliminate some of the virtual calls when the target identity is clear. In additional, final methods or methods in final classes can often be resolved to a single target as well at compile time.

Macronucleus answered 16/6, 2010 at 16:11 Comment(1)
yes exactly, we are aware of power of virtual function, but some time it leads to some misuse of these powers. which turn sometime into bugs. I was looking for that only. Sorry for not describing question well.Malang
P
2

In C++: --

  1. Virtual functions have a slight performance penalty. Normally it is too small to make any difference but in a tight loop it might be significant.

  2. A virtual function increases the size of each object by one pointer. Again this is typically insignificant, but if you create millions of small objects it could be a factor.

  3. Classes with virtual functions are generally meant to be inherited from. The derived classes may replace some, all or none of the virtual functions. This can create additional complexity and complexity is the programmers mortal enemy. For example, a derived class may poorly implement a virtual function. This may break a part of the base class that relies on the virtual function.

Now let me be clear: I am not saying "don't use virtual functions". They are a vital and important part of C++. Just be aware of the potential for complexity.

Ploughman answered 16/6, 2010 at 5:33 Comment(0)
N
2

We recently had a perfect example of how misuse of virtual functions introduces bugs.

There is a shared library that features a message handler:

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

the intent is that you can inherit from that 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 a CMessageHandler* pointer, so it doesn't care of the actual type of the object. The function is virtual, so whenever an overloaded version exists the latter is called.

Cool, right? Yes, it 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.

You see what happened? After the base class was changed the overloads stopped to be the overloads from C++ point of view - they became new, other, unrelated functions.

The base class had the default implementation not marked as pure virtual, so the derived classes were not forced to overload the default implementation. And finally the functon was only called in case of error handling which isn't used every here and there. So the bug was silently introduced and lived unnoticed for a quite long period.

The only way to eliminate it once and for all was to do a search on all the codebase and edit all the relevant pieces of code.

Nydianye answered 16/6, 2010 at 5:49 Comment(2)
good example, but it should say override where it says overload.Chieftain
C++0x has a solution to this problem. It lets you state that you would like an error if a function's name is hidden in a derived class without a specific declaration that that is what your intent is.Disguise
P
2

I dont know where you read that, but imho this is not about performance at all.

Maybe its more about "prefer composition about inheritance" and problems which can occur if your classes/methods are not final (im talking mostly java here) but not really designed for reuse. There are many things which can go really wrong:

  • Maybe you use virtual methods in your constructor - once theyre overridden, your base class calls the overridden method, which may use ressources initialized in the subclass constructor - which runs later (NPE rises).

  • Imagine an add and an addAll method in a list class. addAll calls add many times and both are virtual. Somebody may override them to count how many items have been added at all. If you dont document that addAll calls add, the developer may (and will) override both add and addAll (and add some counter++ stuff to them). But now, if you youse addAll, each item is count twice (add and addAll) which leads to incorrect results and hard to find bugs.

To sum this up, if you dont design your class for being extended (provide hooks, document some of the important implementation things), you shouldnt allow inheritance at all because this can lead to mean bugs. Also its easy to remove a final modifier (and maybe redesign it for reuseability) from one of your classes if needed, but its impossible to make a non-final class (where subclassing lead to errors) final because others may have subclassed it already.

Maybe it was really about performance, then im at least off topic. But if it wasnt, there you have some good reasons not to make your classes extendable if you dont really need it.

More information about stuff like that in Blochs Effective Java (this particular post was written a few days after I read item 16 ("prefer composition over inheritance") and 17 ("design and document for inheritance or else prohibit it") - amazing book.

Palladino answered 16/6, 2010 at 6:17 Comment(1)
Matts answer is really close to mine by the way, i think he edited while i was typing - sorry for duplicated information.Palladino
J
0

I worked sporadically as a consultant on the same C++ system over a period of about 7 years, checking on the work of about 4-5 programmers. Every time I went back the system had gotten worse and worse. At some point somebody decided to remove all the virtual functions and replace them with a very obtuse factory/RTTI-based system that essentially did everything the virtual functions were already doing but worse, more expensively, thousands of lines more code, lots of work, lots of testing, ... Completely and utterly pointless, and clearly fear-of-the-unknown-driven.

They had also hand-written dozens of copy constructors, with errors, when the compiler would have produced them automatically, error-free, with about three exceptions where a hand-written version was required.

Moral: don't fight the language. It gives you things: use them.

Jade answered 17/6, 2010 at 1:22 Comment(0)
A
0

The virtual table gets created for each class, having virtual functions or deriving from a class containing virtual functions. This consumes more than usual space.

The compiler needs to silently insert extra code for ensuring that the late binding takes place instead of the early binding. This consumes more than usual time.

Andvari answered 2/11, 2011 at 10:19 Comment(0)
D
-1

In Java, there is no virtual keyword, but all methods (functions) are virtual, except the ones marked as final, static methods and private instance methods. Using virtual functions is not a bad practice at all, but because generally they cannot be resolved in compile-time, and compiler can't perform optimizations on them, they tend to be a little slower. The JVM has to figure out at run-time, which is the exact method that needs to be called. Note that this is not a big problem by any means, and you should consider it only if your goal is to create a very high-performance application.

For example, one of the biggest optimizations in Apache Spark 2 (which runs on JVM) was to reduce number of virtual function dispatches, to gain a better performance.

Dna answered 7/11, 2018 at 10:4 Comment(1)
The problem with making everything virtual/replaceable is that the semantics of replacing a software component is very rarely defined.Damara

© 2022 - 2024 — McMap. All rights reserved.