我听说很多人都说 C ++模板非常强大。我似乎仍然不理解使用它们而不是使用继承的优点。
由于我主要是一名Java开发人员,认为泛型和模板是同一件事,但相应于Wikipedia:
虽然C ++模板,Java泛型和.NET泛型通常被认为是相似的,但泛型只是模仿C ++模板的基本行为。
我也想知道是否使用模板可以使用类而不是模糊代码?
答案 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)
你会如何正确实施? lhs
和rhs
可能是一切,甚至不能保证两者属于同一类型,所以如果你称之为
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]。