19

我了解 C++ 中模板与 Java 和 C# 中的泛型不同的方面。C# 是一种具体化,Java 使用类型擦除,C++ 使用鸭子类型,等等。C++ 模板可以做许多 Java 和 C# 泛型不能做的事情(例如模板特化)。但是有许多 Java 泛型可以做 C# 和 C++不能做的事情(例如,创建一个泛型家族的有界类型参数,class Foo<T extends Comparable<?>>例如运行时通用反射)。[编辑:显然 Java 泛型比我想象的要弱得多。(这是在说些什么。)无论如何,尽管它们无能,但它们仍然与 C# 的泛型一起被视为泛型。]

我不明白是什么在概念上使模板与泛型不同。C++ 模板的哪些部分是不能在不是模板但是泛型的东西中完成的事情?例如,如果我要实现一种支持模板的语言,那么其中绝对需要什么?对于支持泛型的语言,我可以省略什么?

我的猜测是模板是泛型的超集,或者它们是实现泛型的一种方式,但我真的不明白真正的模板与真正的泛型有什么区别。

4

6 回答 6

9

嗯..如果您说您深入了解 C++ 模板并说您没有看到/感觉泛型与它们之间的区别,那么您很可能是对的 :)

有许多差异将描述泛型如何/为什么比模板更好,列出大量差异等,但这与想法的核心几乎无关。

这个想法是允许更好的代码重用。模板/泛型为您提供了一种构建某种高阶类定义的方法,这些定义抽象了一些实际类型。

在这个术语中,它们之间没有区别,唯一的区别是由底层语言和运行时的特定功能和约束强制执行的区别。

有人可能会争辩说,泛型提供了一些额外的特性(通常在谈论对象类树的动态自省时),但其中很少有(如果有的话)不能在 C++ 的模板中手动实现。通过一些努力,它们中的大多数都可以实现或模拟,因此它们不能很好地区分“适当的泛型”和“真实模板”。

其他人会争辩说,由于 C++ 的复制粘贴行为,优化的巨大潜力是不同的。对不起,不是真的。Java 和 C# 中的 JIT 也可以做到这一点,嗯,几乎,但做得很好。

然而,有一件事确实可以使 Java/C# 的泛型成为 C++ 模板功能的真正子集。你甚至提到了它!

它是模板专业化

在 C++ 中,每个特化都表现为完全不同的定义。

在 C++ 中,template<typename T> Foo专用于 T==int 可能如下所示:

class Foo<int> 
{
    void hug_me();

    int hugs_count() const;
}

而专门用于 T==MyNumericType 的“相同”模板可能看起来像

class Foo<MyNumericType> 
{
    void hug_me();

    MyNumericType get_value() const;
    void  reset_value() const;
}

仅供参考:这只是伪代码,不会编译:)

Java 和 C# 的泛型都不能做到这一点,因为它们的定义声明所有泛型类型实现都将具有相同的“用户界面”。

更重要的是,C++ 使用了 SFINAE 规则。模板可能存在许多“理论上冲突”的专业化定义。但是,在使用模板时,只使用那些“实际上很好”的模板。

使用类似于上面示例的类,如果您使用:

 Foo<double> foood;
 foood.reset_value();

只会使用第二个特化,因为第一个不会编译,因为 ... "reset_value" 缺失。

使用泛型,你不能这样做。您需要创建一个具有所有可能方法的泛型类,然后在运行时动态检查内部对象并为不可用的方法抛出一些“未实现”或“不支持”异常。这……太可怕了。这样的事情在编译时应该是可能的。

模板专业化SFINAE的实际功能、含义、问题和整体复杂性是泛型和模板的真正区别。简单地说,泛型是以这样一种方式定义的,即不可能进行专门化,因此 SFINAE 是不可能的,因此,自相矛盾的是,整个机制更容易/更简单。

在编译器的内部实现更容易/更简单,并且可以被非专家的大脑理解。

尽管我同意 Java/C# 中泛型的整体优势,但我真的很怀念专业化、接口灵活性和 SFINAE 规则。但是,如果我不提及与健全的 OO 设计相关的一件重要事情,我将不公平:如果您对类型 xxx 的模板专业化实际上更改了它的客户端 API,那么很可能它应该命名不同并且应该形成不同的模板. 模板可以做的所有额外好处大部分都添加到了工具集中,因为......在 C++ 中没有反射,它必须以某种方式进行模拟。SFINAE 是编译时反射的一种形式。

因此,差异世界中最大的参与者被简化为用于掩盖运行时缺陷的修补程序的奇怪(有益)副作用,即几乎完全缺乏运行时内省:))

因此,我说除了语言强制执行的一些任意的,或者运行时平台强制执行的一些任意的之外,没有区别。

它们都只是高阶类或函数/方法的一种形式,我认为这是最重要的东西和特性。

于 2013-03-26T11:45:27.253 回答
4

首先,我觉得有趣的是 RTTI/内省是大多数答案的重要组成部分。好吧,这不是泛型与模板的区​​别,而是具有自省的语言与没有自省的语言的区别。否则,您也可以声称这是 C++ 类与 Java 类以及 C++ 函数与 Java 函数的区别......

如果您不考虑自省,主要区别在于模板定义了一种图灵完备的语言,在风格上是功能性的,尽管您可以在其上编写可怕的语法。我听说的第一个非常复杂的例子(我很想拥有代码,但我没有)是一个在编译时计算素数的程序。这确实带来了另一个区别:模板可以采用类型参数、模板参数或非类型参数(非类型指的是任何不是类型或模板的东西,比如一个int值)。

这在其他答案中已经提到过,但只是说模板可以专门化并且有 SFINAE 并没有明确说明这两个功能足以生成图灵完整的语言。

于 2013-03-26T13:38:05.857 回答
3

Java 泛型可以做很多 C# 和 C++ 不能做的事情(例如,创建一个泛型家族的有界类型参数,例如class Foo<T extends Comparable<?>>

该示例并不完全正确:

template <typename Comparable>
struct Foo {
    static bool compare(const Comparable &lhs, const Comparable &rhs) {
        return lhs == rhs;
    }
};

compare只有当模板参数是等式可比类型时,此类模板才会成功实例化函数。它不称为“有界类型参数”,但它具有相同的目的。

如果在 C++ 中您想将Comparable其视为显式接口(即基类)而不是鸭子类型的概念,那么您可以static_assert(is_base_of<Comparable, T>::value, "objects not Comparable");,或其他任何方式。

于 2013-03-26T11:29:20.307 回答
2

不,模板不是泛型的超集,对于 C++ 模板,您没有与 C# 泛型相同级别的运行时支持,这意味着 C++ 中的 RTTI 无法检测并为您提供模板的元数据,如 Reflection 为C# 中的泛型。

除此之外,我喜欢这个片段:

C++ 模板使用编译时模型。在 C++ 程序中使用模板时,效果就像使用了复杂的宏处理器一样。

C# 泛型不仅是编译器的一个特性,也是运行时的一个特性。诸如 List 之类的泛型类型在编译后保持其泛型性(泛型性)。或者,换个角度来看,C++ 编译器在编译时所做的替换是在 C# 泛型世界中的 JIT 时完成的。

完整文章请参见此处:C# 泛型与 C++ 模板相比如何?

于 2013-03-26T10:45:57.203 回答
0

这是一个旧线程,我的代表太低,无法评论接受的答案,但我想补充:

除了显式特化之外,C++ 模板和 C# 泛型的另一个关键区别是 C++ 中使用的非类型模板参数:

template<int bar> class Foo {};

Foo<1> a;
Foo<2> b;

a = b; //error, different types. 

非类型模板参数可以是任何整数类型、枚举以及可以在编译时确定的指针(静态存储变量和函数指针)。在 C++20 中,它们也可以是类类型,但有一定的限制。

C# 和 Java 泛型都不能做到这一点。

您也可以明确地专注于非类型参数。

作为旁注,D 编程语言使用术语“模板”作为泛型编程的命名法,至少在我看来,它的特性在精神上与 C++ 一致,而不是 C#/Java。

我不知道为什么非类型参数被排除在 C# 之外的技术原因,但是作为我现在使用的语言比其他语言更多,我偶尔会错过这个功能。

于 2019-01-04T04:55:00.013 回答
-1

我的答案仅限于 C++ 模板与 Java 泛型。

  1. C++ 模板(类模板和函数模板)是实现编译时多态性的机制,但 AFAIK Java 泛型是运行时机制。
  2. 使用 C++ 模板,您可以进行泛型编程,它实际上是完全独立的编程风格/范式,但 Java 泛型本身就是 OO 风格。见下文:
  3. C++ 模板基于 Duck 类型,但 Java 泛型基于类型擦除。在 C++ 中vector<int>vector<Shape *>vector<double>vector<vector<Matrix>>是 4 种不同的类型,但在 JavaCell<int>中,Cell<Integer> Cell<double>Cell<Matrix>是相同的类型。更准确地说,在代码生成期间,编译器首先会擦除类型。您可以通过以下论文中的以下代码进行检查:Vladimir Batov。Java 泛型和 C++ 模板。C/C++ 用户杂志,2004 年 7 月。

    public class Cell<E>
    {
       private E elem;
       public Cell(E elem)
       { 
          this.elem = elem;
       }
       public E GetElement()
       { 
          return elem;
       }
       public void SetElement(E elem)
       { 
          this.elem = elem;
       } 
    }
    
    boolean test()
    { 
      Cell<Integer> IntCell(10); 
      Cell<String> StrCell(“Hello”);
      return IntCell.getClass() ==
             StrCell.getClass(); // returns true
    }
    

简而言之,Java 假装是通用的,而 C++ 实际上是。

于 2013-03-26T12:08:35.177 回答