Why not use all functions as virtual in C ++?

I know that virtual functions have dereferencing overhead to call a method. But I think that with modern architecture, speed is almost negligible.

  • Is there any special reason why all functions in C ++ are not virtual, as in Java?
  • As far as I know, the definition of the virtual function in the base class is sufficient / necessary. Now that I am writing a parent class, I may not know which methods will be overcome. Does this mean that when writing a child class, someone will have to edit the parent class. Does this sound uncomfortable and sometimes impossible?

Update:
To summarize from Jon Skeet, answer below:

This is a trade-off between explicitly understanding that someone understands that they inherit functionality [which has potential risks in itself [(mark John’s answer)] [and a potential small performance gain] with a trade-off for less flexibility, more code changes and more steep learning curve.

Other reasons from different answers:

Virtual functions cannot be related to each other, since in-process functions must be executed. This affects performance when you expect features to be useful when embedding.

There may be other reasons, and I would like to know and summarize them.

+49
java c ++ virtual-functions
Jul 07 '11 at 6:24
source share
11 answers

There are good reasons to control which methods are virtual beyond performance. Although I actually don't make most of my methods final in Java, I probably should ... unless the method is overridden, it probably shouldn't be a virtual IMO.

Designing for inheritance can be complicated - in particular, this means that you need to document a lot more about what it can call it and what it can call. Imagine if you have two virtual methods, and one calls the other, this must be documented, otherwise someone can override the called method with an implementation that calls the call method, unwittingly creating a stack overflow (or an infinite loop, if tail call optimization). At this point, you get less flexibility in your implementation — you cannot switch it to a later date.

Please note that C # is a similar language in Java in different ways, but by default they prefer to do non-virtual methods. Some other people are not keen on this, but I certainly welcome this, and I would really prefer the default classes to be unacceptable too.

Basically, it comes down to this advice from Josh Bloch: design for inheritance or prohibition.

+71
Jul 07 '11 at 6:29
source share
  • One of the basic principles of C ++: you pay only for what you use (the "principle of zero premium"). If you do not need a dynamic sending mechanism, you do not have to pay its overhead.

  • As the author of the base class, you must decide which methods should be allowed to override. If you write both, go ahead and reorganize what you need. But it works this way, because for the author of the base class there must be a way to control its use.

+48
Jul 07 '11 at 6:27
source share

But I think that with modern architecture, speed is almost negligible.

This assumption is incorrect and, I think, the main reason for this decision.

Consider the case of embedding. The C ++ sort function performs much faster than Cs, otherwise, similar to qsort in some scenarios, because it can inline the comparator argument, and C cannot (due to the use of function pointers). In extreme cases, this can mean a productivity difference of up to 700% (Scott Meyers, Effective STL).

The same can be said of virtual functions. We had similar discussions before; e.g. Is there a reason to use C ++ instead of C, Perl, Python, etc.

+28
Jul 07 '11 at 6:35
source share

Most answers relate to the overhead of virtual functions, but there are other reasons not to do any functions in the virtual class, since the fact that it will change the class from a standard layout to, well, a non-standard layout, and this can be a problem if you need to serialize binary data. This is solved differently in C #, for example, if the struct is a different type family than class es.

From a design point of view, each public function sets up a contract between your type and users of the type, and each virtual function (public or not) sets up a different contract with classes that extend your type. The greater the number of such contracts that you sign, the less opportunities for changes that you have. In fact, there are quite a few people, including well-known writers, who advocate that the public interface should never contain virtual functions, since your compromise for your clients may differ from the compromises that you require from your extensions. That is, the public interfaces show what you are doing for your customers, and the virtual interface shows how others can help you with this.

Another effect of virtual functions is that they are always sent to the final override (unless you explicitly qualify the call), which means that any function needed to maintain your invariants (consider the state of private variables) should not be virtual: if the class expands it, he will have to either make an explicit qualified call to the parent, or otherwise violate the invariants at your level.

This is similar to the example of the infinite loop / stack overflow mentioned in @Jon Skeet, in a different way: you should document in each function whether it receives any private attributes so that the extensions ensure that this function is available at the right time. And this, in turn, means that you break encapsulation, and you have an absorbing abstraction: your internal details are now part of the interface (documentation + requirements for your extensions), and you cannot change them at your discretion.

Then there is performance ... there will be an effect on performance, but in most cases it is overrated, and it can be argued that only in the few cases where performance is critical do you back down and declare the functions non-virtual. Again, this can be difficult for the embedded product, as the two interfaces (public + extensions) are already connected.

+13
Jul 07 2018-11-11T00:
source share

You forgot one thing. The overhead is also in memory, that is, you add a virtual table and a pointer to this table for each object. Now, if you have an object that has a significant number of expected cases, this is not insignificant. For example, a million copies equals 4 megabytes. I agree that for a simple application this is not much, but for real-time devices such as routers, this is taken into account.

+7
Jul 07 2018-11-11T00:
source share

Pay for use (in the words of Bjarn Straustrup).

+5
Jul 07 '11 at 6:32
source share

I'm late to the party here, so add one thing that I didn't notice in the other answers and quickly summarize ...

  • Ease of use in shared memory : a typical virtual dispatch implementation has a pointer to a virtual dispatch table for each object. The addresses in these pointers refer to the process that creates them, which means that systems with multiple processes accessing objects in shared memory cannot be sent using another process object! This is an unacceptable limitation, given the importance of shared memory in high-performance multiprocessor systems.

  • Encapsulation : the ability of a class constructor to manage members that are accessible by client code, while preserving class semantics and invariants. For example, if you exit std::string (I can get some comments for the courage to suggest this; -P), then you can use all the usual insert / erase / add operations and be sure that - if you don't do all that always undefined behavior for std::string , like passing bad position values ​​for functions - the std::string data will be sound. Someone checking or supporting your code should not check if the meaning of these operations has changed. For a class, encapsulation guarantees freedom for subsequent implementation changes without violating the client code. Another perspective is in the same statement: client code can use the class as it sees fit without being sensitive to implementation details. If a function can be changed in a derived class, the entire encapsulation mechanism is simply reset.

    • Hidden dependencies : when you don’t know which other functions depend on which you are redefining, or that the function was intended to be redefined, then you cannot talk about the effect of your change. For example, you think: “I always wanted this” and change std::string::operator[]() and at() to read negative values ​​(after the cast type to sign) will shift back from the end of the line . But perhaps some other function used at() as a kind of statement that the index is valid - knowing that it will return otherwise - before trying to insert or delete ... this code can go from metaling in a standard way to undefined (but probably fatal) behavior.
    • Documentation : By creating a virtual function, you document that it is the intended configuration point and part of the API for client code.

  • Embedding - code usage and CPU usage: virtual dispatching complicates the compiler’s work when you develop built-in function calls, and therefore can provide worse code in terms of both space and bloat CPU usage.

  • Direction during calls : even if a non-contact call is made in any case, there is a small cost for virtual sending, which can be significant when you call trivially simple functions in critical systems. (You should read the pointer to the object for the virtual distribution table, and the virtual dispatch table entry itself means that VDT pages also consume cache.)

  • Memory usage : object pointers for virtual send tables can represent significant memory loss, especially for arrays of small objects. This means that fewer objects are cached and can have a significant impact on performance.

  • Memory layout : it is necessary for performance and is very convenient for interaction, that C ++ can define classes with the exact layout of layouts of member data defined by network standards or data from various libraries and protocols. This data often comes from outside your C ++ program and can be generated in another language. Such communication and storage protocols will not have “spaces” for pointers to virtual dispatch tables and, as discussed earlier, even if they did, and the compiler somehow allowed you to efficiently enter the correct pointers for your process according to the incoming data, which could disrupt multiprocessor data access. A brown but practical code based on pointer / size will also be more complex and potentially slow.

+5
Jul 08 2018-11-11T00:
source share

It seems that this question may have multiple answers. Virtual functions cannot be overused - Why? . In my opinion, the one thing that stands out is that it simply increases the complexity in terms of knowing what can be done with inheritance.

+3
Jul 07 2018-11-11T00:
source share

Yes, this is due to overhead. Virtual methods are invoked using virtual tables and indirect references.

In Java, all methods are virtual and overhead is also present. But, contrary to C ++, the JIT compiler profiles the code at runtime and can embed methods that do not use this property. In this way, the JVM knows where it really is needed and where it does not free you from deciding on your own.

+2
Jul 07 '11 at 6:32
source share

The problems are that although Java is compiled for code that runs on a virtual machine, the same guarantee cannot be performed for C ++. It is customary to use C ++ as a more organized replacement for C, and C has a 1: 1 translation on the assembly.

If you think that 9 out of 10 microprocessors in the world are not in a personal computer or smartphone, you will see this problem, given that there are many processors that need this access at a low level.

C ++ was designed to avoid hidden delay, if you did not need it, thereby preserving the nature of 1: 1. Some of the first C ++ code actually had an intermediate step in switching to C before running through the C-to-assembly compiler.

+1
Jul 07 '11 at 6:32
source share

Calling Java methods is much more efficient than C ++ because of the optimization of runtime.

We need to compile C ++ into bytecode and run it on the JVM.

-four
Jul 07 '11 at 7:15
source share



All Articles