【问题标题】:Why does one need templates/generics? Isn't inheritance enough?为什么需要模板/泛型?继承还不够吗?
【发布时间】:2011-10-12 09:58:23
【问题描述】:

我听到很多人说C++ 模板非常强大。我似乎仍然不明白使用它们而不是使用继承的好处。

由于我主要是一名 Java 开发人员,我认为 generics 和 templates 是一回事,但根据 Wikipedia:

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

我还想知道,在可能只使用类的地方使用模板是否不会混淆代码?

【问题讨论】:

  • 你用过std::vector吗?
  • 您不需要模板/泛型(毕竟,它们只是在 java 1.5 中引入的),但是它们提供了不错的功能。类型安全的集合(例如 STL 中的 std::vector )就是其中之一:您将如何仅使用继承来实现一个? (例如,Vector 的 add 方法需要接受 Object 的任何子类)。这种参数化在没有“根类”的 C++ 中更为重要(如 java 中的 Object)。
  • 这是一个完全有效的问题
  • 由于这个问题已经结束,我想指出 C++ 模板允许“静态多态性”(参见 CRTP),这是您在 Java 中无法做到的。此外,您可以有整数参数,因此您可以在编译时指定类数组的大小,例如,不需要使用动态分配的内存。在 C++0x 中,数组类使用 this。
  • 我认为,如果您在聊天中提出问题(如果您愿意的话)或者如果您有适合 Q/A 格式的内容:“我知道怎么写某某与继承,模板会是什么样子,我会获得/权衡什么”。

标签: java c++ templates generics inheritance


【解决方案1】:

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

【讨论】:

  • @Luc:你说得对,“正确”不是正确的词!让我改变它。
  • 或者更准确地说:std::vector<T, Allocator>。我同意 Luc Danton 的观点。
【解决方案2】:

如果你有一个类型不可知的方法,比如 (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-code)

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

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

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

运行时调度(虚拟函数等)也总是有潜在的成本。通过 C++ 模板的专用代码仅针对您所调用的内容进行实例化,即不会产生额外的性能成本。代码膨胀可以忽略不计,因为 C++ 模板是惰性实例化的(只有您真正调用的内容才会发送到实际代码),并且与手动专用(“重载”)代码相比,它并不多一个字节。

C++ 世界不仅仅由 OOP [-动态多态性] 组成。

【讨论】:

    【解决方案3】:

    在过去,您会使用:

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

    如果您在列表中的某个位置插入不同类型的值,您只会在检索对象时找到,而没有关于真正失败位置的信息(插入错误类型的对象)。

    现在,你做:

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

    并在错误所在的地方抛出错误。

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

    【讨论】:

    • 是的,我知道这一点。谢谢!
    【解决方案4】:

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

    考虑一个简单的例子,在 Java 中,List。当你有一个只应该包含的列表时,我不知道,客户,但如果实际上它包含一堆对象引用,你不能保证它不包含一堆动物或数据库连接,当你把它拿回来时,你必须施放它,它可以扔。泛型保证正确的结果,不需要强制转换。如果你写了一个错误,试图在这里插入一些不属于的东西,你的编译器就会出错。这种安全级别远远高于多态性所能提供的。

    此外,模板(在 C++ 中)可以接受类型以外的参数,例如整数类型,可以执行编译时类型自省等等。模板的可能性远远大于继承的可能性,而且更安全、更快。

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

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

    实际上,这些参数与静态和动态类型基本相同,其中模板是静态的,而继承是动态的。与动态类型相比,静态类型明显更快且不易出错。

    【讨论】:

      【解决方案5】:

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

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

      最后,如果您使用的模板只是一个简单的类就可以了 工作,你在滥用模板。仅仅因为 C++ 有模板 并不意味着你应该让每个类和函数都成为一个模板。

      【讨论】:

        【解决方案6】:

        我...认为泛型和模板是一回事

        实际上它们几乎是截然相反的。 C++ 模板生成新类型。 Java 泛型限制现有类型。真的是粉笔和奶酪。有点遗憾的是它们有相似的语法。

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

          • 我用 Ruby 和 Python 完成了元编程。从来不知道模板与它有关。谢谢!
          猜你喜欢
          • 1970-01-01
          • 2013-01-11
          • 1970-01-01
          • 1970-01-01
          • 2010-09-18
          • 1970-01-01
          • 2011-06-17
          • 2011-03-05
          • 1970-01-01
          相关资源
          最近更新 更多