是什么让模板与通用模板不同?

时间:2013-03-26 10:40:07

标签: c# java c++ templates generics

我理解C ++中与Java和C#中的泛型不同的模板方面。 C#是一种物化,Java使用类型擦除,C ++使用duck typing等.C ++模板可以做许多事情,Java和C#泛型不能(例如模板特化)。但是有一些泛型可以做的事情,C#和C ++无法做到(例如,制作一个像class Foo<T extends Comparable<?>>这样的泛型系列的有界类型参数,并且很多东西C#泛型可以做到Java和C ++不能(例如运行时泛型反射)。 [编辑:显然Java泛型比我想象的要弱得多。 (这是在说些什么。)无论如何,尽管它们不合适,但它们仍然被认为是仿制药和C#的仿制药。]

我不明白的是概念上使模板与泛型不同。 C ++模板的哪些部分是不能在不是模板的东西中完成的东西,而是通用的?例如,如果我要实现支持模板的语言,那么绝对需要什么呢?我可以忽略哪些语言支持泛型所必需的?

我的猜测是模板是一组超级泛型,或者它们是实现泛型的一种方式,但我并不真正理解真正模板与真正模板之间的区别。

6 个答案:

答案 0 :(得分:8)

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

有很多不同之处将描述仿制药如何/为什么比模板更好,列出大量差异等等,但这与这个想法的核心无关。

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

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

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

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

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

模板专业化

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

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

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是编译时反思的一种形式。

因此,差异世界中最大的玩家被降低到应用于掩盖运行时缺陷的修补程序的好奇(有益)副作用,这几乎完全缺乏运行时内省:))

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

所有这些只是高阶类或函数/方法的一种形式,我认为这是最重要的事情和特征。

答案 1 :(得分:4)

首先,我发现有趣的是RTTI /内省是大多数答案的重要组成部分。嗯,这不是泛型与模板的区​​别,而是具有instrospection的语言与没有它的语言。否则你也可以声称C ++类与Java类不同,C ++函数与Java函数不同......

如果你不去考虑,那么主要区别在于模板定义了一种图灵完整的语言,虽然语法很糟糕,但你可以编程。我听说的第一个非常复杂的例子(我希望有代码,但我不喜欢)是一个在编译时计算素数的程序。这确实带来了另一个区别:模板可以采用类型参数,模板参数或非类型参数(非类型指的是任何不是类型或模板的东西,如int值)。

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

答案 2 :(得分: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");或其他什么。

答案 3 :(得分:2)

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

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

  

C ++模板使用编译时模型。在a中使用模板时   C ++程序,效果好像是一个复杂的宏处理器   已被使用。

     

C#泛型不仅是编译器的一个特性,也是一个特性   运行时。像List这样的泛型类型维护它   编译后的泛型(通用性)。或者,看看   换句话说,C ++编译器在编译时所做的替换   时间在JIT时间在C#通用世界完成。

请参阅此处查看完整文章:How do C# generics compare to C++ templates?

答案 4 :(得分:0)

这是一个旧线程,我的代表太低,无法对accepted answer进行评论,但是我想补充:

除了显式专业化之外,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#/ Java更符合C ++。

我不知道为什么非类型参数会遗漏在C#中的技术原因,但是由于我现在使用的语言比其他语言多,所以我偶尔会错过该功能。

答案 5 :(得分:-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种不同的类型,但在Java Cell<int>中,Cell<Integer> Cell<double>Cell<Matrix>属于同一类型。更确切地说,在代码生成期间 编译器首先删除类型。 您可以按照以下论文中的代码进行检查: 弗拉基米尔·巴托夫。 Java泛型和C ++模板。 C / C ++ Users Journal,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
    }
    
  4. 简而言之,Java假装是通用的,而C ++实际上是。