有没有真正的理由不在 C++中使成员函数虚拟化?当然,总会存在性能参数,但由于虚函数的开销相当低,因此在大多数情况下似乎并不存在.
在另一方面,我已经被咬了好几次与遗忘做一个功能的虚拟那应该是虚拟的.这似乎是一个比表现更大的争论.那么有没有理由不默认将成员函数设为虚拟?
读取问题的一种方法是"为什么C++默认情况下不会使每个函数都是虚拟的,除非程序员重写默认值." 如果不查阅我的"C++的设计和演变"副本:这将为每个类增加额外的存储空间,除非每个成员函数都是非虚拟的.在我看来,这需要在编译器实现上付出更多的努力,并通过为痴迷的性能提供素材来减慢C++的采用(我自己也算在那个组中.)
另一种阅读问题的方法是"为什么C++程序员不会将每个函数都虚拟化,除非他们有充分的理由不这样做?" 性能借口可能是原因.这取决于您的应用程序和域,这可能是一个很好的理由.例如,我的团队的一部分在市场数据自动收录机工厂工作.在单个流上以100,000多条消息/秒,虚拟功能开销将是不可接受的.我团队的其他部分在复杂的交易基础设施中工作.在这种情况下,将大多数功能虚拟化可能是一个好主意,因为额外的灵活性优于微优化.
该语言的设计者Stroustrup 说:
因为许多类不是设计用作基类.例如,请参阅类复杂.
此外,具有虚函数的类的对象需要虚函数调用机制所需的空间 - 通常每个对象一个字.这种开销可能很大,并且可能妨碍布局与其他语言(例如C和Fortran)的数据兼容.
有关更多设计原理,请参阅C++的设计和演变.
有几个原因.
首先,性能:是的,虚拟功能的开销相对较低.但它也阻止了编译器的内联,这是C++中一个巨大的优化源.C++标准库的功能与它一样好,因为它可以内联它所包含的几十个和一些小的单行程序.此外,具有虚方法的类不是POD数据类型,因此有很多限制.它不能仅仅通过memcpy来复制,构建起来会变得更加昂贵,占用更多空间.一旦涉及非POD类型,有很多事情突然变得非法或效率降低.
第二,良好的OOP实践.类中的一点是它进行某种抽象,隐藏其内部细节,并保证"这个类将表现得如此,并且将始终保持这些不变量.它永远不会以无效状态结束" .如果允许其他人覆盖任何成员函数,那就很难实现.您在类中定义的成员函数用于确保维护不变量.如果我们不关心这一点,我们可以公开内部数据成员,让人们随意操纵它们.但我们希望我们的班级保持一致.这意味着我们必须指定其公共接口的行为.这可能涉及具体的可定制点,通过使单个函数虚拟化,但它几乎总是涉及使大多数方法非虚拟,以便它们可以完成确保维持不变量的工作.非虚拟界面习语就是一个很好的例子:http: //www.gotw.ca/publications/mill18.htm
第三,通常不需要继承,尤其是在C++中.模板和泛型编程(静态多态)在许多情况下可以比继承(运行时多态)做得更好.是的,您有时仍需要虚方法和继承,但它肯定不是默认方法.如果是的话,你做错了.使用该语言,而不是试图假装它是其他东西.它不是Java,与Java不同,C++继承是例外,而不是规则.
我会忽略性能和内存成本,因为我没有办法测量它们的"一般"情况......
具有虚拟成员函数的类是非POD.因此,如果您希望在依赖于POD的低级代码中使用您的类,那么(除其他限制之外)任何成员函数都必须是非虚拟的.
您可以使用POD类的实例进行移植的示例:
用memcpy复制它(假设目标地址有足够的对齐).
使用offsetof()访问字段
通常,将其视为一系列char
嗯
这就是它.我确定我忘记了什么.
人们提到的其他事我同意:
许多类不是为继承而设计的.使他们的方法变得虚拟会产生误导,因为它暗示子类可能想要覆盖该方法,并且不应该有任何子类.
许多方法都不是为了覆盖而设计的:同样的事情.
此外,即使意图进行子类化/重写,它们也不一定用于运行时多态.偶尔,尽管OO最佳实践说,你想继承的是代码重用.例如,如果您使用CRTP进行模拟动态绑定.因此,你再次不希望暗示你的类通过使其方法成为虚拟的方式可以很好地运行运行时多态性,而不应该以这种方式调用它们.
总之,那些旨在被覆盖的运行时多态性的东西应该被标记为虚拟,而不应该被标记为虚拟的东西.如果您发现几乎所有成员函数都是虚拟的,那么将它们标记为虚拟,除非有理由不这样做.如果您发现大多数成员函数都不是虚拟的,那么除非有理由这样做,否则不要将它们标记为虚拟.
在设计公共API时这是一个棘手的问题,因为将方法从一个方法转换到另一个方法是一个重大变化,所以你必须在第一时间做到正确.但是,在您拥有任何用户之前,您不一定知道您的用户是否想要"更改"您的类.哼哼.定义抽象接口和完全禁止继承的STL容器方法是安全的,但有时需要用户进行更多的输入.
以下帖子主要是意见,但这里有:
面向对象设计是三件事,封装(信息隐藏)是这些事情的第一件事.如果一个班级设计在这方面不是很扎实,那么剩下的并不重要.
之前已经说过"继承打破了封装"(Alan Snyder '86)对这一点的一个很好的讨论存在于四个设计模式书的组中.应该设计一个类以非常特定的方式支持继承.否则,你就有可能被继承人误用.
我会假设所有的方法都是虚拟的类似于让所有成员公开.我知道,有点拉伸,但这就是为什么我用'类比'这个词