如今,关于C ++的许多演讲都涉及模板及其在编译时多态性实现中的用法。虚拟函数和运行时多态性几乎没有讨论。
我们可以在许多情况下使用编译时多态。并且由于它为我们提供了编译时检查,而不是与运行时多态性相关的可能的运行时错误,以及一些(通常是微不足道的)性能优势,因此,当今看来,使用最广泛的库比运行时更喜欢编译时多态性一个。
但是,对我来说,用C ++模板实现的编译时多态性看起来比虚拟类型层次结构的自记录性和可读性代码要少得多。
作为现实生活中的例子,我们可以回顾boost::iostreams
。它实现stream
作为模板,接受设备类作为参数。当将特定功能的实现划分为许多类和文件并放在不同文件夹中时,会导致这种情况,因此,与像Java和.NET Framework中的流那样使用虚拟函数来形成类层次结构的流相比,用这种代码进行调查要复杂得多。这里的编译时多态性有什么好处?文件流是可以读写文件的东西,流是可以读写的东西(任何东西),它是类型层次结构的经典示例,因此为什么不使用单个FileStream
类来重载一些受保护的函数,而不是在语义上进行划分将功能整合到不同的文件和类中?
另一个示例是boost::process::child
类。它使用模板化的构造函数来设置标准I / O和其他过程参数。它没有充分的文档记录,从该函数原型中也不清楚此模板将接受哪种格式的参数。类似于SetStandardOutput
的成员函数的实现将更好地自我记录,并导致更快的编译时间,那么在这里使用模板有什么好处?再次,我在这里将此实现与.NET Framework进行比较。对于类似于SetStandardOutput
的成员函数,只需读取单个头文件即可了解如何使用该类。对于boost::process::child
的模板化构造函数,我们必须读取许多小文件。
有很多与此示例相似的示例。出于任何原因,众所周知的开源库几乎都不会使用虚拟类层次结构,而更喜欢像boost
那样使用编译时多态性(主要是基于模板的)。
问题:在我们可以同时使用两者的情况下,我们有没有什么明确的准则需要我们首选(编译时或运行时多态)?
答案 0 :(得分:3)
通常来说,在90%的情况下,模板和虚拟功能是可互换的。
首先,我们需要澄清我们在说什么。如果您“比较”某项,则在某些条件下必须等效。我对您的声明的理解不是将virtual functions
与templates
进行比较,而是在polymorphism
的上下文中进行比较!
在这种情况下,您的示例选择不佳,而且dynamic cast
更像是工具箱中的“大锤子”,就像我们谈论多态性一样。
您的“模板示例”不需要使用模板,因为您具有简单的重载,而这些重载完全不需要任何模板代码即可使用!
如果我们谈论的是polymorphism
和c++
,那么我们首先选择的是runtime-polymorphism
和compile-time
多态。对于这两者,我们都有c ++的标准解决方案。对于运行时,我们使用虚拟函数,对于编译时多态,我们将CRTP作为典型实现,而不将模板作为通用术语!
当我们不得不使用丑陋的模板语法而不是更易理解和紧凑的虚函数和继承语法时,C ++委员会或任何其他权威人士有任何意见或建议吗?
如果您习惯使用它,语法并不难看!如果我们正在谈论使用SFINAE实现事物,那么我们会有一些难以理解的带有模板实例化的规则,尤其是经常被误解的deduced context
。
但是在C ++ 20中,我们将拥有concepts,它可以在大多数情况下替代SFINAE,这是我相信的一件好事。用概念而不是SFINAE编写代码可以使代码更具可读性,更易于维护,并且可以更轻松地扩展新类型和“规则”。
标准库(如果包含模板)并且具有非常有限的虚拟功能。这是否意味着我们必须尽可能地避免使用虚函数,并且始终偏爱模板,即使它们在某些特定任务中的语法不那么紧凑且难以理解。
该问题使您感到误解了C ++功能。模板允许用户编写generic code
,而virtual functions
是实现runtime polymorphism
的C ++工具。没有可比的1:1。
在阅读示例代码时,我建议您再考虑一下您的编码风格!
如果要编写针对不同数据类型的函数,只需像在“模板”示例中一样使用重载即可,但不需要不必要的模板!
如果要在同一代码中实现适用于不同数据类型的通用函数,请使用模板,如果特定数据类型需要一些特殊代码,请对所选的特定代码部分使用模板专门化。
如果您需要更多需要SFINAE的选择性模板代码,则应从c ++ 20概念开始实施。
如果要实现polymorphism
,请决定使用run time
或compile time
多态。如前所述,对于第一个函数,虚函数是实现该功能的标准C ++工具,而CRTP是第二个函数的标准解决方案之一。
我对“动态投射”的个人经验是:避免它!通常,这是第一个暗示您的设计有问题的提示。这不是一般规则,而是重新考虑设计的检查点。在极少数情况下,它是合适的工具。而且RTTI并非适用于所有目标,并且有一些开销。在裸机设备/嵌入式系统上,有时不能使用RTTI,也不能使用例外。如果打算将您的代码用作您域中的“平台”,并且您具有上述限制,请不要使用RTTI!
编辑:评论中的答案
因此,就目前而言,使用C ++,我们只能使类具有运行时多态性。
不! CRTP还构建类层次结构,但用于编译时多态。但是,由于您没有“通用”基类,因此解决方案完全不同。但是,由于所有这些都是在编译时解决的,因此对通用基类没有技术上的需求。您只应该开始阅读有关Mixins的内容,也许在这里:What are Mixins (as a concept)和CRTP作为实现方法之一:CRTP article wikipedia。
不知道如何在没有运行时开销的情况下实现类似于虚拟功能的东西。
请参见上文,CRTP和Mixin完全实现了多态性,而没有运行时开销!
模板为实现这一目标提供了可能性。
模板只是基本的C ++工具。模板与C ++中的循环级别相同。在这种情况下,说“模板”要广泛得多。
因此,如果我们需要类层次结构,是否就意味着我们必须使用它,甚至会迫使我们使用更少的编译时间检查?
如上所述,类层次结构只是实现多态性任务的解决方案的一部分。想更多地考虑要实现的逻辑事物,例如多态性,序列化程序,数据库或其他什么,以及实现方案,例如虚拟函数,循环,堆栈,类等。“编译时间检查”?在大多数情况下,您不必自己编写“检查”。一个简单的重载类似于编译时的if / else,它“检查”数据类型。因此,只需直接使用它即可,无需模板或SFINAE。
或者我们必须使用模板来实现某种编译时类层次结构,即使这样会使我们的语法更加紧凑和易于理解
已经提到:模板代码是可读的! std::enable_if
更易于阅读,因为它们是一些手工制作的SFINAE东西,即使它们都使用相同的C ++模板机制。而且,如果您熟悉c ++ 20概念,那么您将有很大的机会在即将到来的c ++版本中编写更具可读性的模板代码。