为什么需要模板/泛型?遗产不够吗?

时间:2011-07-25 08:42:16

标签: java c++ templates generics inheritance

我听说很多人都说 C ++模板非常强大。我似乎仍然不理解使用它们而不是使用继承的优点。

由于我主要是一名Java开发人员,认为泛型模板是同一件事,但相应于Wikipedia

  

虽然C ++模板,Java泛型和.NET泛型通常被认为是相似的,但泛型只是模仿C ++模板的基本行为。

我也想知道是否使用模板可以使用类而不是模糊代码?

7 个答案:

答案 0 :(得分:14)

模板和继承履行不同的角色,而且非常罕见 你可以在哪里选择。一个非常简单的描述应该 是模板提供了不同的通用实现 接口,其中继承为其提供通用接口 不同的实现。在他们通常的角色中,模板强制执行 编译时类型系统的不变量;考虑一些 预模板库,其中Vector只能包含Object*, 一切都必须来自Object(你必须把东西装箱 比如int)。将int插入向量,然后尝试读取 double,是一个运行时错误(或简称为未定义的行为) 比编译时错误。

我不同意维基百科的引用:技术上,C ++模板 和Java模板几乎不相关。 Java模板的目标是 在类型上提供不变量的编译时强制 系统,这也是C ++模板的重要用途之一,但是 使用的机制完全不相关,C ++模板也可以 也用于其他目的。

最后,如果你正在使用一个简单的类会做的模板 这份工作,你在滥用模板。仅仅因为C ++有模板 并不意味着你应该让每个类都成为一个模板。

答案 1 :(得分:13)

模板在编译时发生。继承发生在运行时。你可以在编译时捕获模板的错误,你必须对继承进行单元测试(然后希望你不要错过它们)。此外,模板比继承更清晰,更流畅。

考虑Java中List的简单情况。如果你有一个只应包含的List,我不知道,客户,但如果实际上它拥有一堆Object引用,你不能保证它不包含一堆动物或DatabaseConnections,当你得到它,你必须投,它可以扔。泛型保证正确的结果,不需要演员表。如果你写了一个试图在这里插入一些不属于的东西的bug,你的编译器会抛出一个拟合。这种安全级别远远高于多态性所能提供的级别。

此外,模板(在C ++中)可以接受除类型之外的参数,例如整数类型,可以执行编译时类型内省,以及那种事情。模板的可能性大大超过了继承的可能性,而且它也更加安全和快捷。

最后,使用模板,您不必显式继承。如果我想提供operator+,我是否必须继承Addable?不,模板会自动提取。这比必须明确指定我可以继承的每个功能更加可维护,因为当新库出现时,我不必更改我的代码以继承其Addable接口。在C ++中,我不必从Callable接口继承以允许使用boost::function,即使它是在我的函数对象编写之后开发的。 Java永远不会用继承做到这一点。你怎么可能开发一个可以处理可变数量的参数而没有泛型的类?写一个Callable1,Callable2,Callable3接口?

考虑另一个简单的例子。假设我想将两个对象一起添加,然后用最终对象减去结果。如果您有一个IAddable接口,您怎么可能指定加法运算的结果是可以减去的?并且可以减少什么 - 我当然希望这不是另一个参数?您基本上必须为每个复杂用例编写一个新接口。另一方面,模板会一直保持其类型信息,因此,如果我执行的操作不止一次,结果就不会丢失信息。

这些基本上与静态和动态类型相同,有效地,模板是静态的,继承是动态的。与动态类型相比,静态类型明显更快,更不容易出错。

答案 2 :(得分:3)

在过去,你会使用:

 List myList = new ArrayList();
 myList.add(new String("Hello"));
 ....

 String myString = (String) myList.get(0);

如果您在列表中插入不同类型的值,则只能在检索对象时找到,而不会知道实际失败的位置(插入错误类型的对象)。

现在,你这样做:

 List<String> myList = new ArrayList<String>();

并且在错误发生的地方抛出错误。

这已经被“过去”控制了,但它涉及扩展ArrayList或创建一个包装器,因此它只接受有效的类;泛型简化了很多。

答案 3 :(得分:2)

  

我...认为泛型和模板是同一个东西

实际上他们几乎是两极。 C ++模板生成新类型。 Java Generics限制现有类型。粉笔和奶酪真的。他们有类似的语法,这有点不幸。

答案 4 :(得分:2)

模板还有助于元编程 - 一种可用于生成编译时代码(循环,多函数,递归,编译时计算等),静态断言实现,限制仅适用于特定类的类的技术type(比如int,或者定义了一些运算符/方法)。

虽然对于初学者来说元编程很难理解和适应,但是已经被广泛使用,特别是在Boost中。

答案 5 :(得分:1)

继承是不够的。作为示例,请考虑std::vector(或者可能更相关,std::vector<T>)。它为您提供了一个在编译时类型安全(对于任何T)的容器类。继承是不可能的。

答案 6 :(得分:1)

如果你有一个类型不可知的方法,如(p-code)

void add (object lhs, object rhs)

你会如何正确实施? lhsrhs可能是一切,甚至不能保证两者属于同一类型,所以如果你称之为

add ("foo@example.com", 3)
add (std::vector<int>, std::pair<Foo, float>() )
add (add, add)

使用模板/泛型,您可以是显式的(p代码)

template <PARAM_TYPE>
void add (PARAM_TYPE lhs, PARAM_TYPE rhs)

这也意味着您可以保护类型安全。

这只是多态性不会削减的众多例子之一。此外,泛型与模板实际上并不相同,因此实际上很难给出一个很好的定制答案。

运行时调度(虚拟功能等)也总是带来潜在成本。通过C ++模板的专用代码只是为了你所称的实例化,即没有额外的性能成本。代码膨胀是微不足道的,因为C ++模板是懒惰地实例化的(只有你真正称之为发送到实际代码的东西)并且不是手动专用(“重载”)代码的字节。

C ++世界不仅仅包含OOP [-dynamic polymorphism]。