为什么不让c++中的所有函数都是虚拟的呢?

时间:2022-09-08 16:54:16

I know that virtual functions have an overhead of dereferencing to call a method. But I guess with modern architectural speed it is almost negligible.

我知道虚函数有取消引用调用方法的开销。但我想,以现代建筑的速度,它几乎可以忽略不计。

  1. Is there any particular reason why all functions in C++ are not virtual as in Java?
  2. 为什么c++中的所有函数都不像Java中的那样是虚拟的?
  3. From my knowledge, defining a function virtual in a base class is sufficient/necessary. Now when I write a parent class, I might not know which methods would get over-ridden. So does that mean that while writing a child class someone would have to edit the parent class. This sounds like inconvenient and sometimes not possible?
  4. 根据我的知识,在基类中定义一个虚函数是足够的/必要的。现在,当我编写父类时,我可能不知道哪些方法会被重写。这是否意味着在编写子类时,必须有人编辑父类。这听起来很不方便,有时也不可能?

Update:
Summarizing from Jon Skeet's answer below:

更新:以下是Jon Skeet的回答总结:

It's a trade-off between explicitly making someone realize that they are inheriting functionality [which has potential risks in themselves [(check Jon's response)] [and potential small performance gains] with a trade-off for less flexibility, more code changes, and a steeper learning curve.

明确地让某人意识到他们继承了功能(这本身就有潜在的风险(请检查Jon的响应))和潜在的小性能收益之间的权衡,以换取更少的灵活性、更多的代码更改和更陡峭的学习曲线。

Other reasons from different answers:

不同回答的其他原因:

Virtual functions cannot be in-lined because inlining have to happen at runtime. This have performance impacts when you expect you functions benefits from inlining.

虚函数不能内联,因为内联必须在运行时发生。当您期望功能从内联中获益时,这会对性能产生影响。

There might be potentially other reasons, and I would love to know and summarize them.

可能还有其他潜在的原因,我很想知道并总结一下。

11 个解决方案

#1


74  

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

有充分的理由来控制哪些方法是虚拟的而不是性能。虽然我实际上并没有让我的大多数方法成为Java的最终版本,但我可能应该……除非一个方法被设计为被重写,否则它可能不应该是虚拟的IMO。

Designing for inheritance can be tricky - in particular it means you need to document far more about what might call it and what it might call. Imagine if you have two virtual methods, and one calls the other - that must be documented, otherwise someone could override the "called" method with an implementation which calls the "calling" method, unwittingly creating a stack overflow (or infinite loop if there's tail call optimization). At that point you've then got less flexibility in your implementation - you can't switch it round at a later date.

为继承而设计是很棘手的——特别是它意味着您需要记录更多关于它可能调用什么以及它可能调用什么。想象一下,如果您有两个虚拟方法,其中一个调用另一个——这必须被记录下来,否则某人可以用一个调用“调用”方法的实现覆盖“调用”方法,无意中创建一个堆栈溢出(如果有尾部调用优化,则创建无限循环)。到那时,实现的灵活性就会降低——您不能在以后的时间里进行切换。

Note that C# is a similar language to Java in various ways, but chose to make methods non-virtual by default. Some other people aren't keen on this, but I certainly welcome it - and I'd actually prefer that classes were uninheritable by default too.

注意,c#在许多方面与Java类似,但在默认情况下,它选择使方法非虚拟的。有些人对此不感兴趣,但我当然欢迎它——实际上我更希望类在默认情况下也是不可继承的。

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

基本上,它归结于Josh Bloch的建议:为继承而设计或禁止它。

#2


49  

  1. One of the main C++ principles is: you only pay for what you use ("zero overhead principle"). If you don't need the dynamic dispatch mechanism, you shouldn't pay for its overhead.

    c++的一个主要原则是:您只需要为您所使用的东西付费(“零开销原则”)。如果不需要动态调度机制,则不应该为其开销付费。

  2. As the author of the base class, you should decide which methods should be allowed to be overridden. If you're writing both, go ahead and refactor what you need. But it works this way, because there has to be a way for the author of the base class to control its use.

    作为基类的作者,您应该决定应该允许覆盖哪些方法。如果你同时写这两篇文章,那就去重构你需要的东西。但是它是这样工作的,因为必须有一种方法可以让基类的作者控制它的使用。

#3


28  

But I guess with modern architectural speed it is almost negligible.

但我想,以现代建筑的速度,它几乎可以忽略不计。

This assumption is wrong, and, I guess, the main reason for this decision.

这个假设是错误的,我猜,这也是这个决定的主要原因。

Consider the case of inlining. C++’ sort function performs much faster than C’s otherwise similar qsort in some scenarios because it can inline its comparator argument, while C cannot (due to use of function pointers). In extreme cases, this can mean performance differences of as much as 700% (Scott Meyers, Effective STL).

考虑内联的情况。在某些场景中,c++排序函数的执行速度要比C的qsort快得多,因为它可以内联比较器参数,而C不能(由于使用了函数指针)。在极端情况下,这可能意味着性能差异高达700% (Scott Meyers, Effective STL)。

The same would be true for virtual functions. We’ve had similar discussions before; for instance, Is there any reason to use C++ instead of C, Perl, Python, etc?

对于虚函数也是如此。我们之前也有过类似的讨论;例如,是否有理由使用c++而不是C、Perl、Python等等?

#4


13  

Most answers deal with the overhead of virtual functions, but there are other reasons not to make any function in a class virtual, as the fact that it will change the class from standard-layout to, well, non-standard-layout, and that can be a problem if you need to serialize binary data. That is solved differently in C#, for example, by having structs being a different family of types than classes.

大多数答案都处理虚函数的开销,但是还有其他原因不让类中的任何函数成为虚函数,因为它会将类从标准布局更改为非标准布局,如果需要序列化二进制数据,这可能是一个问题。这在c#中得到了不同的解决,例如,将结构设置为与类不同的类型族。

From the design point of view, every public function establishes a contract between your type and the users of the type, and every virtual function (public or not) establishes a different contract with the classes that extend your type. The greater the number of such contracts that you sign the less room for changes that you have. As a matter of fact, there are quite a few people, including some well known writers, that defend that the public interface should never contain virtual functions, as your compromise to your clients might be different from the compromises you require from your extensions. That is, the public interfaces shows what you do for your clients, while the virtual interface shows how others might help you in doing it.

从设计的角度来看,每个公共函数在类型和类型的用户之间建立一个契约,每个虚拟函数(公共的或非公共的)与扩展类型的类建立一个不同的契约。您签署的此类合同越多,更改的空间就越小。事实上,有很多人,包括一些知名的作者,都认为公共接口不应该包含虚拟功能,因为您对客户端的妥协可能与您对扩展的妥协不同。也就是说,公共接口显示了您为客户端做了什么,而虚拟接口则显示了其他人可能如何帮助您做这件事。

Another effect of virtual functions is that they always get dispatched to the final overrider (unless you explicitly qualify the call), and that means that any function that is needed to maintain your invariants (think the state of the private variables) should not be virtual: if a class extends it, it will have to either make an explicit qualified call back to the parent or else would break the invariants at your level.

虚函数的另一个效应是,他们总是派到最后保险杠挡块(除非你明确限定的调用),这意味着任何函数,需要保持你的不变量(想想私有变量的状态)不应该虚拟:如果一个类扩展了它,它将不得不做出一个明确的合格的父母回电话,否则将打破你的水平的不变量。

This is similar to the example of the infinite loop/stack overflow that @Jon Skeet mentioned, just in a different way: you have to document in each function whether it accesses any private attributes so that extensions will ensure that the function is called at the right time. And that in turn means that you are breaking encapsulation and you have a leaking abstraction: Your internal details are now part of the interface (documentation + requirements on your extensions), and you cannot modify them as you wish.

这与@Jon Skeet提到的无限循环/堆栈溢出示例类似,只是方式不同:您必须在每个函数中记录它是否访问任何私有属性,以便扩展将确保在正确的时间调用函数。而这又意味着您正在破坏封装,并且您有一个泄漏的抽象:您的内部细节现在是接口的一部分(文档+对您的扩展的要求),并且您不能按照您的意愿修改它们。

Then there is performance... there will be an impact in performance, but in most cases that is overrated, and it could be argued that only in the few cases where performance is critical, you would fall back and declare the functions non-virtual. Then again, that might not be simple on a built product, since the two interfaces (public + extensions) are already bound.

然后是性能……在性能方面会有影响,但是在大多数情况下,这是被高估的,并且可以认为只有在少数情况下,性能是关键的,您才会后退并声明函数是非虚的。同样,对于构建的产品来说,这可能并不简单,因为两个接口(公共+扩展)已经被绑定了。

#5


7  

You forget one thing. The overhead is also in memory, that is you add a virtual table and a pointer to that table for each object. Now if you have an object which has significant number of instances expected then it is not negligible. example, million instance equals 4 Mega byte. I agree that for simple application this is not much, but for real time devices such as routers this counts.

你忘记了一件事。开销也在内存中,即为每个对象添加一个虚拟表和一个指向该表的指针。如果你有一个对象,它有相当数量的实例,那么它是不可忽略的。例如,百万实例等于4百万字节。我同意,对于简单的应用程序,这并不多,但对于实时设备(如路由器),这很重要。

#6


5  

Pay per use (in Bjarne Stroustrup words).

按使用付费(用Bjarne Stroustrup的话)。

#7


5  

I'm rather late to the party here, so I'll add one thing that I haven't noticed covered in other answers, and summarise quickly...

我来晚了,所以我要补充一件我没有注意到的事情,在其他答案中也有提到,然后快速总结……

  • Usability in shared memory: a typical implementation of virtual dispatch has a pointer to a class-specific virtual dispatch table in each object. The addresses in these pointers are specific to the process creating them, which means multi-process systems accessing objects in shared memory can't dispatch using another process's object! That's an unacceptable limitation given shared memory's importance in high-performance multi-process systems.

    共享内存中的可用性:虚拟分派的典型实现具有指向每个对象中特定于类的虚拟分派表的指针。这些指针中的地址是特定于创建它们的过程的,这意味着在共享内存中访问对象的多进程系统不能使用另一个进程的对象来调度!考虑到共享内存在高性能多进程系统中的重要性,这是一个不可接受的限制。

  • Encapsulation: the ability of a class designer to control the members accessed by client code, ensuring class semantics and invariants are maintained. For example, if you derive from std::string (I may get a few comments for daring to suggest that ;-P) then you can use all the normal insert / erase / append operations and be sure that - provided you don't do anything that's always undefined behaviour for std::string like pass bad position values to functions - the std::string data will be sound. Someone checking or maintaining your code doesn't have to check if you've changed the meaning of those operations. For a class, encapsulation ensures freedom to later modify the implementation without breaking client code. Another perspective on the same statement: client code can use the class any way it likes without being sensitive to the implementation details. If any function can be changed in a derived class, that whole encapsulation mechanism is simply blown away.

    封装:类设计器能够控制客户端代码访问的成员,确保维护类语义和不变量。举个例子,如果你来自std::string(我可能得到一个大胆的表明一些评论;- p),那么你可以使用所有正常插入/删除/添加操作和确保——如果你不做任何事情,总是未定义行为的std::string传递坏的位置值等功能,std::string数据将声音。检查或维护代码的人不需要检查您是否更改了这些操作的含义。对于类来说,封装确保了以后在不破坏客户端代码的情况下修改实现的*。同一语句的另一个视角是:客户端代码可以任意使用类,而不需要对实现细节敏感。如果可以在派生类中更改任何函数,那么整个封装机制就会被忽略。

    • Hidden dependencies: when you know neither what other functions are dependent on the one you're overriding, nor that the function was designed to be overridden, then you can't reason about the impact of your change. For example, you think "I've always wanted this", and change std::string::operator[]() and at() to consider negative values (after a type-cast to signed) to be offsets backwards from the end of the string. But, perhaps some other function was using at() as a kind of assertion that an index was valid - knowing it'll throw otherwise - before attempting an insertion or deletion... that code might go from throwing in a Standard-specified way to having undefined (but likely lethal) behaviour.
    • 隐藏的依赖关系:当您不知道其他函数依赖于您所重写的一个函数,也不知道该函数被设计为被重写时,那么您就不能解释更改的影响。例如,您认为“我一直想要这个”,并将std::string:::operator[]()和at()更改为从字符串末尾向后偏移值(在类型转换为符号之后)。但是,在尝试插入或删除之前,可能有其他函数使用at()作为一种断言,表明索引是有效的——知道它会抛出其他的东西……这段代码可能会从采用标准指定的方式转变为具有未定义(但可能是致命的)行为。
    • Documentation: by making a function virtual, you're documenting that it is an intended point of customisation, and part of the API for client code to use.

    • 文档:通过使一个函数虚拟,您正在文档化它是定制的目标点,并且是客户端代码使用的API的一部分。
  • Inlining - code side & CPU usage: virtual dispatch complicates the compiler's job of working out when to inline function calls, and could therefore provide worse code in terms of both space/bloat and CPU usage.

    内联—代码端和CPU使用:虚拟分派使编译器计算何时内联函数调用变得复杂,因此可能在空间/膨胀和CPU使用方面提供更差的代码。

  • Indirection during calls: even if an out-of-line call is being made either way, there's a small performance cost for virtual dispatch that may be significant when calling trivially simple functions repeatedly in performance critical systems. (You have to read the per-object pointer to the virtual dispatch table, then the virtual dispatch table entry itself - means the VDT pages are consuming cache too.)

    调用期间的间接性:即使以任何一种方式进行脱机调用,在性能关键系统中反复调用简单的函数时,虚拟调度的性能成本也很小,这可能是非常重要的。(必须读取到虚拟调度表的每个对象指针,然后读取虚拟调度表条目本身——这意味着VDT页面也在消耗缓存。)

  • Memory usage: the per-object pointers to virtual dispatch tables may represent significant wasted memory, especially for arrays of small objects. This means less objects fit in cache, and can have a significant performance impact.

    内存使用:指向虚拟分派表的每个对象指针可能表示严重的内存浪费,特别是对于小对象的数组。这意味着适合缓存的对象更少,并且会对性能产生重大影响。

  • Memory layout: it's essential for performance, and highly convenient for interoperability, that C++ can define classes with the exact memory layout of member data specified by network or data standards of various libraries and protocols. That data often comes from outside your C++ program, and may be generated in another language. Such communications and storage protocols won't have "gaps" for pointers to virtual dispatch tables, and as discussed earlier - even if they did, and the compiler somehow let you efficiently inject the correct pointers for your process over incoming data, that would frustrate multi-process access to the data. Crude-but-practical pointer/size based serialisation/deserialisation/comms code would also be made more complicated and potentially slower.

    内存布局:对于性能和互操作性非常重要,c++可以使用网络或各种库和协议的数据标准指定的成员数据的精确内存布局来定义类。这些数据通常来自于c++程序之外,并且可以用另一种语言生成。这样的通信和存储协议不会有指向虚拟调度表的指针的“间隔”,而且正如前面所讨论的那样——即使有,编译器也会以某种方式让您在传入数据上有效地为您的进程注入正确的指针,这将阻碍对数据的多进程访问。基于crude -but-实用的指针/大小的序列化/反序列化/comms代码也会变得更加复杂,并且可能会更慢。

#8


3  

Seems like this question might have some answers Virtual functions should not be used excessively - Why ?. In my opinion the one thing that stands out is that it just add more complexity in terms of knowing what can be done with inheritance.

似乎这个问题可能有一些答案虚拟函数不应该被过度使用-为什么?在我看来,最突出的一点是,它只是增加了更多的复杂性,因为它知道继承可以做什么。

#9


2  

Yes, it's because of performance overhead. Virtual methods are called using virtual tables and indirection.

是的,这是因为性能开销。使用虚拟表和间接调用虚拟方法。

In Java all methods are virtual and the overhead is also present. But, contrary to C++, the JIT compiler profiles the code during run-time and can in-line those methods which don't use this property. So, JVM knows where it's really needed and where not thus freeing You from making the decision on your own.

在Java中,所有的方法都是虚拟的,开销也是存在的。但是,与c++相反,JIT编译器在运行时对代码进行概要分析,并可以在不使用此属性的方法中进行内联。因此,JVM知道哪些地方真正需要它,哪些地方不需要它,从而使您不必自己做决定。

#10


1  

The issues is that while Java compiles to code that runs on a virtual machine, that same guarantee can't be made for C++. It common to use C++ as a more organized replacement for C, and C has a 1:1 translation to assembly.

问题是,虽然Java编译为在虚拟机上运行的代码,但c++不能提供同样的保证。使用c++作为C的更有组织的替换是很常见的,而且C对汇编有1:1的转换。

If you consider that 9 out of 10 microprocessors in the world are not in a personal computer or a smartphone, you'll see the issue when you further consider that there are a lot of processors that need this low level access.

如果你考虑到世界上每10个微处理器中有9个不是在个人电脑或智能手机中,那么当你进一步考虑到有许多处理器需要这样的低级别访问时,你就会发现这个问题。

C++ was designed to avoid that hidden deferencing if you didn't need it, thus keeping that 1:1 nature. Some of the first C++ code actually had an intermediate step of being translated to C before running through a C-to-assembly compiler.

c++的设计是为了避免在不需要的情况下隐藏的延迟,因此保持了1:1的特性。一些最初的c++代码实际上有一个中间步骤,就是在通过cto -assembly编译器运行之前被翻译成C。

#11


-4  

Java method calls are far more efficient than C++ due to runtime optimization.

由于运行时优化,Java方法调用比c++高效得多。

What we need is to compile C++ into bytecode and run it on JVM.

我们需要的是将c++编译成字节码,并在JVM上运行它。

#1


74  

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

有充分的理由来控制哪些方法是虚拟的而不是性能。虽然我实际上并没有让我的大多数方法成为Java的最终版本,但我可能应该……除非一个方法被设计为被重写,否则它可能不应该是虚拟的IMO。

Designing for inheritance can be tricky - in particular it means you need to document far more about what might call it and what it might call. Imagine if you have two virtual methods, and one calls the other - that must be documented, otherwise someone could override the "called" method with an implementation which calls the "calling" method, unwittingly creating a stack overflow (or infinite loop if there's tail call optimization). At that point you've then got less flexibility in your implementation - you can't switch it round at a later date.

为继承而设计是很棘手的——特别是它意味着您需要记录更多关于它可能调用什么以及它可能调用什么。想象一下,如果您有两个虚拟方法,其中一个调用另一个——这必须被记录下来,否则某人可以用一个调用“调用”方法的实现覆盖“调用”方法,无意中创建一个堆栈溢出(如果有尾部调用优化,则创建无限循环)。到那时,实现的灵活性就会降低——您不能在以后的时间里进行切换。

Note that C# is a similar language to Java in various ways, but chose to make methods non-virtual by default. Some other people aren't keen on this, but I certainly welcome it - and I'd actually prefer that classes were uninheritable by default too.

注意,c#在许多方面与Java类似,但在默认情况下,它选择使方法非虚拟的。有些人对此不感兴趣,但我当然欢迎它——实际上我更希望类在默认情况下也是不可继承的。

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

基本上,它归结于Josh Bloch的建议:为继承而设计或禁止它。

#2


49  

  1. One of the main C++ principles is: you only pay for what you use ("zero overhead principle"). If you don't need the dynamic dispatch mechanism, you shouldn't pay for its overhead.

    c++的一个主要原则是:您只需要为您所使用的东西付费(“零开销原则”)。如果不需要动态调度机制,则不应该为其开销付费。

  2. As the author of the base class, you should decide which methods should be allowed to be overridden. If you're writing both, go ahead and refactor what you need. But it works this way, because there has to be a way for the author of the base class to control its use.

    作为基类的作者,您应该决定应该允许覆盖哪些方法。如果你同时写这两篇文章,那就去重构你需要的东西。但是它是这样工作的,因为必须有一种方法可以让基类的作者控制它的使用。

#3


28  

But I guess with modern architectural speed it is almost negligible.

但我想,以现代建筑的速度,它几乎可以忽略不计。

This assumption is wrong, and, I guess, the main reason for this decision.

这个假设是错误的,我猜,这也是这个决定的主要原因。

Consider the case of inlining. C++’ sort function performs much faster than C’s otherwise similar qsort in some scenarios because it can inline its comparator argument, while C cannot (due to use of function pointers). In extreme cases, this can mean performance differences of as much as 700% (Scott Meyers, Effective STL).

考虑内联的情况。在某些场景中,c++排序函数的执行速度要比C的qsort快得多,因为它可以内联比较器参数,而C不能(由于使用了函数指针)。在极端情况下,这可能意味着性能差异高达700% (Scott Meyers, Effective STL)。

The same would be true for virtual functions. We’ve had similar discussions before; for instance, Is there any reason to use C++ instead of C, Perl, Python, etc?

对于虚函数也是如此。我们之前也有过类似的讨论;例如,是否有理由使用c++而不是C、Perl、Python等等?

#4


13  

Most answers deal with the overhead of virtual functions, but there are other reasons not to make any function in a class virtual, as the fact that it will change the class from standard-layout to, well, non-standard-layout, and that can be a problem if you need to serialize binary data. That is solved differently in C#, for example, by having structs being a different family of types than classes.

大多数答案都处理虚函数的开销,但是还有其他原因不让类中的任何函数成为虚函数,因为它会将类从标准布局更改为非标准布局,如果需要序列化二进制数据,这可能是一个问题。这在c#中得到了不同的解决,例如,将结构设置为与类不同的类型族。

From the design point of view, every public function establishes a contract between your type and the users of the type, and every virtual function (public or not) establishes a different contract with the classes that extend your type. The greater the number of such contracts that you sign the less room for changes that you have. As a matter of fact, there are quite a few people, including some well known writers, that defend that the public interface should never contain virtual functions, as your compromise to your clients might be different from the compromises you require from your extensions. That is, the public interfaces shows what you do for your clients, while the virtual interface shows how others might help you in doing it.

从设计的角度来看,每个公共函数在类型和类型的用户之间建立一个契约,每个虚拟函数(公共的或非公共的)与扩展类型的类建立一个不同的契约。您签署的此类合同越多,更改的空间就越小。事实上,有很多人,包括一些知名的作者,都认为公共接口不应该包含虚拟功能,因为您对客户端的妥协可能与您对扩展的妥协不同。也就是说,公共接口显示了您为客户端做了什么,而虚拟接口则显示了其他人可能如何帮助您做这件事。

Another effect of virtual functions is that they always get dispatched to the final overrider (unless you explicitly qualify the call), and that means that any function that is needed to maintain your invariants (think the state of the private variables) should not be virtual: if a class extends it, it will have to either make an explicit qualified call back to the parent or else would break the invariants at your level.

虚函数的另一个效应是,他们总是派到最后保险杠挡块(除非你明确限定的调用),这意味着任何函数,需要保持你的不变量(想想私有变量的状态)不应该虚拟:如果一个类扩展了它,它将不得不做出一个明确的合格的父母回电话,否则将打破你的水平的不变量。

This is similar to the example of the infinite loop/stack overflow that @Jon Skeet mentioned, just in a different way: you have to document in each function whether it accesses any private attributes so that extensions will ensure that the function is called at the right time. And that in turn means that you are breaking encapsulation and you have a leaking abstraction: Your internal details are now part of the interface (documentation + requirements on your extensions), and you cannot modify them as you wish.

这与@Jon Skeet提到的无限循环/堆栈溢出示例类似,只是方式不同:您必须在每个函数中记录它是否访问任何私有属性,以便扩展将确保在正确的时间调用函数。而这又意味着您正在破坏封装,并且您有一个泄漏的抽象:您的内部细节现在是接口的一部分(文档+对您的扩展的要求),并且您不能按照您的意愿修改它们。

Then there is performance... there will be an impact in performance, but in most cases that is overrated, and it could be argued that only in the few cases where performance is critical, you would fall back and declare the functions non-virtual. Then again, that might not be simple on a built product, since the two interfaces (public + extensions) are already bound.

然后是性能……在性能方面会有影响,但是在大多数情况下,这是被高估的,并且可以认为只有在少数情况下,性能是关键的,您才会后退并声明函数是非虚的。同样,对于构建的产品来说,这可能并不简单,因为两个接口(公共+扩展)已经被绑定了。

#5


7  

You forget one thing. The overhead is also in memory, that is you add a virtual table and a pointer to that table for each object. Now if you have an object which has significant number of instances expected then it is not negligible. example, million instance equals 4 Mega byte. I agree that for simple application this is not much, but for real time devices such as routers this counts.

你忘记了一件事。开销也在内存中,即为每个对象添加一个虚拟表和一个指向该表的指针。如果你有一个对象,它有相当数量的实例,那么它是不可忽略的。例如,百万实例等于4百万字节。我同意,对于简单的应用程序,这并不多,但对于实时设备(如路由器),这很重要。

#6


5  

Pay per use (in Bjarne Stroustrup words).

按使用付费(用Bjarne Stroustrup的话)。

#7


5  

I'm rather late to the party here, so I'll add one thing that I haven't noticed covered in other answers, and summarise quickly...

我来晚了,所以我要补充一件我没有注意到的事情,在其他答案中也有提到,然后快速总结……

  • Usability in shared memory: a typical implementation of virtual dispatch has a pointer to a class-specific virtual dispatch table in each object. The addresses in these pointers are specific to the process creating them, which means multi-process systems accessing objects in shared memory can't dispatch using another process's object! That's an unacceptable limitation given shared memory's importance in high-performance multi-process systems.

    共享内存中的可用性:虚拟分派的典型实现具有指向每个对象中特定于类的虚拟分派表的指针。这些指针中的地址是特定于创建它们的过程的,这意味着在共享内存中访问对象的多进程系统不能使用另一个进程的对象来调度!考虑到共享内存在高性能多进程系统中的重要性,这是一个不可接受的限制。

  • Encapsulation: the ability of a class designer to control the members accessed by client code, ensuring class semantics and invariants are maintained. For example, if you derive from std::string (I may get a few comments for daring to suggest that ;-P) then you can use all the normal insert / erase / append operations and be sure that - provided you don't do anything that's always undefined behaviour for std::string like pass bad position values to functions - the std::string data will be sound. Someone checking or maintaining your code doesn't have to check if you've changed the meaning of those operations. For a class, encapsulation ensures freedom to later modify the implementation without breaking client code. Another perspective on the same statement: client code can use the class any way it likes without being sensitive to the implementation details. If any function can be changed in a derived class, that whole encapsulation mechanism is simply blown away.

    封装:类设计器能够控制客户端代码访问的成员,确保维护类语义和不变量。举个例子,如果你来自std::string(我可能得到一个大胆的表明一些评论;- p),那么你可以使用所有正常插入/删除/添加操作和确保——如果你不做任何事情,总是未定义行为的std::string传递坏的位置值等功能,std::string数据将声音。检查或维护代码的人不需要检查您是否更改了这些操作的含义。对于类来说,封装确保了以后在不破坏客户端代码的情况下修改实现的*。同一语句的另一个视角是:客户端代码可以任意使用类,而不需要对实现细节敏感。如果可以在派生类中更改任何函数,那么整个封装机制就会被忽略。

    • Hidden dependencies: when you know neither what other functions are dependent on the one you're overriding, nor that the function was designed to be overridden, then you can't reason about the impact of your change. For example, you think "I've always wanted this", and change std::string::operator[]() and at() to consider negative values (after a type-cast to signed) to be offsets backwards from the end of the string. But, perhaps some other function was using at() as a kind of assertion that an index was valid - knowing it'll throw otherwise - before attempting an insertion or deletion... that code might go from throwing in a Standard-specified way to having undefined (but likely lethal) behaviour.
    • 隐藏的依赖关系:当您不知道其他函数依赖于您所重写的一个函数,也不知道该函数被设计为被重写时,那么您就不能解释更改的影响。例如,您认为“我一直想要这个”,并将std::string:::operator[]()和at()更改为从字符串末尾向后偏移值(在类型转换为符号之后)。但是,在尝试插入或删除之前,可能有其他函数使用at()作为一种断言,表明索引是有效的——知道它会抛出其他的东西……这段代码可能会从采用标准指定的方式转变为具有未定义(但可能是致命的)行为。
    • Documentation: by making a function virtual, you're documenting that it is an intended point of customisation, and part of the API for client code to use.

    • 文档:通过使一个函数虚拟,您正在文档化它是定制的目标点,并且是客户端代码使用的API的一部分。
  • Inlining - code side & CPU usage: virtual dispatch complicates the compiler's job of working out when to inline function calls, and could therefore provide worse code in terms of both space/bloat and CPU usage.

    内联—代码端和CPU使用:虚拟分派使编译器计算何时内联函数调用变得复杂,因此可能在空间/膨胀和CPU使用方面提供更差的代码。

  • Indirection during calls: even if an out-of-line call is being made either way, there's a small performance cost for virtual dispatch that may be significant when calling trivially simple functions repeatedly in performance critical systems. (You have to read the per-object pointer to the virtual dispatch table, then the virtual dispatch table entry itself - means the VDT pages are consuming cache too.)

    调用期间的间接性:即使以任何一种方式进行脱机调用,在性能关键系统中反复调用简单的函数时,虚拟调度的性能成本也很小,这可能是非常重要的。(必须读取到虚拟调度表的每个对象指针,然后读取虚拟调度表条目本身——这意味着VDT页面也在消耗缓存。)

  • Memory usage: the per-object pointers to virtual dispatch tables may represent significant wasted memory, especially for arrays of small objects. This means less objects fit in cache, and can have a significant performance impact.

    内存使用:指向虚拟分派表的每个对象指针可能表示严重的内存浪费,特别是对于小对象的数组。这意味着适合缓存的对象更少,并且会对性能产生重大影响。

  • Memory layout: it's essential for performance, and highly convenient for interoperability, that C++ can define classes with the exact memory layout of member data specified by network or data standards of various libraries and protocols. That data often comes from outside your C++ program, and may be generated in another language. Such communications and storage protocols won't have "gaps" for pointers to virtual dispatch tables, and as discussed earlier - even if they did, and the compiler somehow let you efficiently inject the correct pointers for your process over incoming data, that would frustrate multi-process access to the data. Crude-but-practical pointer/size based serialisation/deserialisation/comms code would also be made more complicated and potentially slower.

    内存布局:对于性能和互操作性非常重要,c++可以使用网络或各种库和协议的数据标准指定的成员数据的精确内存布局来定义类。这些数据通常来自于c++程序之外,并且可以用另一种语言生成。这样的通信和存储协议不会有指向虚拟调度表的指针的“间隔”,而且正如前面所讨论的那样——即使有,编译器也会以某种方式让您在传入数据上有效地为您的进程注入正确的指针,这将阻碍对数据的多进程访问。基于crude -but-实用的指针/大小的序列化/反序列化/comms代码也会变得更加复杂,并且可能会更慢。

#8


3  

Seems like this question might have some answers Virtual functions should not be used excessively - Why ?. In my opinion the one thing that stands out is that it just add more complexity in terms of knowing what can be done with inheritance.

似乎这个问题可能有一些答案虚拟函数不应该被过度使用-为什么?在我看来,最突出的一点是,它只是增加了更多的复杂性,因为它知道继承可以做什么。

#9


2  

Yes, it's because of performance overhead. Virtual methods are called using virtual tables and indirection.

是的,这是因为性能开销。使用虚拟表和间接调用虚拟方法。

In Java all methods are virtual and the overhead is also present. But, contrary to C++, the JIT compiler profiles the code during run-time and can in-line those methods which don't use this property. So, JVM knows where it's really needed and where not thus freeing You from making the decision on your own.

在Java中,所有的方法都是虚拟的,开销也是存在的。但是,与c++相反,JIT编译器在运行时对代码进行概要分析,并可以在不使用此属性的方法中进行内联。因此,JVM知道哪些地方真正需要它,哪些地方不需要它,从而使您不必自己做决定。

#10


1  

The issues is that while Java compiles to code that runs on a virtual machine, that same guarantee can't be made for C++. It common to use C++ as a more organized replacement for C, and C has a 1:1 translation to assembly.

问题是,虽然Java编译为在虚拟机上运行的代码,但c++不能提供同样的保证。使用c++作为C的更有组织的替换是很常见的,而且C对汇编有1:1的转换。

If you consider that 9 out of 10 microprocessors in the world are not in a personal computer or a smartphone, you'll see the issue when you further consider that there are a lot of processors that need this low level access.

如果你考虑到世界上每10个微处理器中有9个不是在个人电脑或智能手机中,那么当你进一步考虑到有许多处理器需要这样的低级别访问时,你就会发现这个问题。

C++ was designed to avoid that hidden deferencing if you didn't need it, thus keeping that 1:1 nature. Some of the first C++ code actually had an intermediate step of being translated to C before running through a C-to-assembly compiler.

c++的设计是为了避免在不需要的情况下隐藏的延迟,因此保持了1:1的特性。一些最初的c++代码实际上有一个中间步骤,就是在通过cto -assembly编译器运行之前被翻译成C。

#11


-4  

Java method calls are far more efficient than C++ due to runtime optimization.

由于运行时优化,Java方法调用比c++高效得多。

What we need is to compile C++ into bytecode and run it on JVM.

我们需要的是将c++编译成字节码,并在JVM上运行它。