模板与泛型的区别
What makes a template different from a generic?
我了解c++中模板与Java和c#中的泛型不同的方面。c#是一种具体化,Java使用类型擦除,c++使用鸭子类型,等等。c++模板可以做很多Java和c#泛型不能做的事情(比如模板专门化)。但是,有许多Java泛型可以做的事情,c#和c++做不到(例如,为一系列泛型(如class Foo<T extends Comparable<?>>
)创建有界类型参数),并且有许多c#泛型可以做的事情,Java和c++做不到(例如,运行时泛型反射)。[编辑:显然Java泛型比我想象的要弱得多。](这说明了一些问题。)在任何情况下,尽管它们很笨拙,它们仍然和c#的泛型一样被认为是泛型[/em>
我不明白的是什么在概念上使模板不同于泛型。c++模板的哪些部分是不能在非模板的泛型中完成的?例如,如果我要实现一种支持模板的语言,其中绝对需要什么?对于语言支持泛型,我可以省略哪些必要的内容?
我的猜测是模板是泛型的超集,或者它们是实现泛型的一种方式,但我真的不明白真正的模板和真正的泛型之间的区别。
嗯…如果你说你对c++模板有深入的了解,并说你没有看到/感觉到泛型和它们之间的区别,那么,很可能你是对的:)
有许多不同之处可以描述泛型如何/为什么比模板更好,列出了大量的不同之处,等等,但这大多与思想的核心无关。
这个想法是为了允许更好的代码重用。模板/泛型为您提供了一种构建某种高阶类定义的方法,这些定义抽象于某些实际类型之上。
在这种情况下,它们之间没有区别,唯一的区别是由底层语言和运行时的特定特性和约束所强制执行的。
有人可能会争辩说泛型提供了一些额外的特性(通常是在谈论对象类树的动态自省时),但是很少有(如果有的话)不能在c++的模板中手动实现。通过一些努力,它们中的大多数都可以实现或模拟,因此它们不能很好地区分"适当的泛型"answers"真正的模板"。其他人会争辩说,由于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是一种编译时反射。
因此,差异世界中最大的玩家被简化为一个奇怪的(有益的)副作用,用于掩盖运行时的缺陷,这是几乎完全缺乏运行时自省:))
因此,我说除了一些由语言强制执行的任意规则,或者一些由运行时平台强制执行的任意规则之外,没有什么区别。
它们都是高阶类或函数/方法的一种形式,我认为这是最重要的东西和特性。
首先,我发现RTTI/自省是大多数答案的重要组成部分,这很有趣。这不是泛型和模板的区别,而是有内省的语言和没有内省的语言的区别。否则,你也可以声称这是c++类与Java类的区别,c++函数与Java函数的区别…
如果不考虑内省,主要区别在于模板定义了一种完整的语言,函数式的风格,尽管语法很糟糕,你可以在上面编程。我听说的第一个真正复杂的例子(我很想知道代码,但我没有)是一个在编译时计算素数的程序。这确实带来了另一个区别:模板可以接受类型参数,或模板参数或非类型参数(非类型指的是任何不是类型或模板的东西,如int
值)。
这在其他答案中已经提到过,但是仅仅说模板可以专门化并且存在SFINAE并没有清楚地说明这两个特性足以生成图灵完整的语言。
有许多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");
,或者其他什么。
不,模板不是泛型的超集,对于c++模板,你没有与c#泛型相同级别的运行时支持,这意味着c++中的RTTI无法像c#中的反射对泛型那样检测和提供模板的元数据。
除了,我喜欢这个片段:
c++模板使用编译时模型。模板在c++程序,其效果就像一个复杂的宏处理器被使用。
c#泛型不仅是编译器的一个特性,也是一个特性运行时的。泛型类型(如List)维护其编译后的泛型(泛型)。或者,看换句话说,c++编译器在compile时做的替换在c#泛型世界中,time是在JIT时间完成的。
查看全文:c#泛型与c++模板相比如何?
这是一个旧的线程,我的代表太低了,不能评论接受的答案,但我想补充:
除了显式特化之外,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#中没有非类型参数的技术原因,但是作为一种我最近比其他语言使用得更多的语言,我偶尔会想念这个特性。
我将把我的答案限制在c++模板与Java泛型之间。
- c++模板(类模板和函数模板)是实现编译时多态性,但Java泛型是运行时的机制。
- 使用c++模板,你可以做泛型编程,它实际上是是完全独立的编程风格/范例,但Java泛型是OO风格本身。见下文:
-
c++模板是基于Duck类型的,而Java泛型是基于类型擦除。在c++中,
vector<int>
,vector<Shape *>
,vector<double>
和vector<vector<Matrix>>
是4种不同的类型,但是在Java中Cell<int>
,Cell<Integer>
Cell<double>
和Cell<Matrix>
是同一类型。更准确地说,是在代码生成期间编译器首先擦除类型。您可以通过以下代码检查它:弗拉基米尔•Batov。Java泛型和c++模板。C/c++ Users Journal, July 2004.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++实际上是。
- 错误处理.将系统错误代码映射到泛型
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 链表的泛型函数remove()与成员函数remove)
- 给定一个类型,如何派生一个泛型更广泛的类型(例如,用于溢出安全求和)?
- 模板化接口 - 创建一个泛型模板类以返回任何容器
- 如何编写将要继承的泛型代码?
- C++17 如何保存泛型可调用对象以供以后使用
- 使用宏扩展的泛型:为什么指令缓存使用不当?
- C++泛型类错误,问题出在哪里?
- C++泛型类,单独实现?
- 将参数传递给泛型 lambda 时复制构造函数不正确
- 泛型枚举和其他类型的重载模板函数
- 使用泛型类型推送到堆栈时出现问题
- 可变参数泛型 lambda 和函数重载
- C++ 泛型和多态性:这种模式可行吗?
- 这些语句是否等效(静态变量、常量变量和泛型)
- Java 是否像C++模板一样具有泛型推论?
- 为堆栈实现泛型集合
- 以特征类型作为参数的泛型函数回调
- 模板与泛型的区别