大家好,我对模板和多态性有疑问。根据定义,多态性提供代码可重用性,并且模板在某种意义上允许用户通过提供具有不同数据类型的通用编程来使用相同的代码。那么在模板上使用多态性有什么好处。这可能是一个愚蠢的问题,但我很想知道确切的区别。
2 回答
您似乎误解了多态性是什么。
多态性的核心与派生类无关。多态性仅仅意味着在不知道所有类型的情况下使用类型的能力。多态性不是使用具体类型,而是依赖于某种形式的原型来定义它所采用的类型。任何适合该原型的类型都被接受。
C++ 中的运行时多态性是通过从包含虚函数的基类派生类来提供的。基类和虚函数构成多态原型。为接受调用这些虚函数的基类而编写的代码将接受从基类派生的任何类实例。
编译时多态性是在编译时发生的多态性;)这意味着编译器必须知道发生了什么。您可能已经针对多态原型编写了 C++ 代码,但编译器并不关心。您会在编译后获得特定的具体类型。
编译时多态性由 C++ 中的模板提供。模板函数或类可以采用符合原型的任何类型,通常称为“概念”。与基类和虚函数不同,原型是隐式的:原型仅由模板函数/类如何使用类型来定义。
如果你有这个模板功能:
template<typename T>
void Stuff(T &t)
{
t.call(15);
}
有一个隐含的要求T
。这个要求是它有一个名为 的成员函数call
。此成员函数必须有一个可以用整数值调用的重载。
这意味着可以使用恰好适合此原型的任何类型。
模板多态比继承多态更广泛,因为它可以被更广泛的类型数组使用。必须专门设计一个类型以使用继承多态性;你必须从一个类派生。类型可以非破坏性地(即:您不必更改类型本身)适应模板多态性。如果您的模板原型设计良好,则更是如此:
template<typename T>
void Stuff(T &t)
{
call(t, 15);
}
这个版本的所有Stuff
要求是有一些函数接受一个T&
和一个整数值。如果我想使用某种类型,我Stuff
所要做的就是call
在适当的命名空间(即定义该类型的命名空间)中定义一个函数。这将工作得很好。所有这些都无需修改类型本身。
当然,编译时多态性是……编译时。如果我想要一些用户输入或数据文件来选择多态类型,模板不会有很大帮助(尽管类型擦除,一种基于模板的技术,可以提供帮助)。运行时多态的主要好处是它确实是运行时的。
另一个好处是它的原型更精确。一切都明确说明了继承。基类中的虚函数接口布局清晰。编译器将阻止您尝试错误地使用该基类(调用其上不存在的方法)。事实上,一个体面的 IDE 将指导您的代码,以便您只能看到基类上的方法。
模板多态性要隐含得多。由于 C++ 无法说明特定模板函数/类放在类型上的原型,因此很容易意外地在模板类型上调用一些你不应该调用的东西。只有当您尝试使用不适合原型的类型时,编译器才会检测到这一点。即使这样,您通常也会收到大量错误(取决于您的模板代码嵌套的深度),这使得很难知道问题出在哪里。
实现隐式模板多态原型也困难得多,因为它没有被详细说明。实现派生类需要遍历基类,查看所有虚函数并实现它们。为模板原型执行此操作要困难得多,除非某处有说明它的文档。如果你没有实现某些东西,你会再次得到一个错误,通常不会出现问题。
简而言之,它归结为您对要解决的问题的共性和可变性分析的结果。
如果您对不同的东西执行相同的操作,那么您将使用模板List<int>
,例如List<float>
如果您根据上下文以不同的方式对相同的东西执行相同的操作,这也具有一些共性,那么您将使用多态性,例如AbstractInterface::openFile
带有导数的接口WindowsInterface::openFile
and LinuxInterface::openFile
,其中之一将根据上下文使用。抽象接口标识了在概念上执行相同操作这一事实的共性,派生项实现了概念实现的可变性。