C ++模板和Java / C#泛型之间有什么区别?有什么限制?

时间:2013-04-07 00:30:56

标签: c# java c++ generics template-meta-programming

我从here读了一篇有趣的文章/话题/讨论,我得到了以下问题:

  • Java / C#泛型有哪些限制?
  • 使用Java / C#泛型无法实现的C ++模板有哪些功能?

修改1 Eric Lippert

提出的更多推荐问题
  • 使用C#泛型可以实现哪些模式,但使用C ++模板却无法实现?
  • C#的真正泛型类型和Java类型擦除泛型类型之间有什么区别?

4 个答案:

答案 0 :(得分:9)

首先,您可能需要阅读my 2009 article on this subject

C ++模板和C#泛型之间的主要区别在于,C ++模板实际上在构建模板时完全重新编译代码。 C ++方法的优点和缺点很多:

  • PRO:您可以有效地创建约束,例如“类型参数T必须有一个加法运算符”;如果代码中包含几个相互添加的Ts,那么如果使用不允许添加的类型参数构造模板,模板将无法编译。

  • CON:您可能会意外地创建未记录的约束,例如“类型参数T必须具有加法运算符”。

在C#中你必须哪些约束对用户有帮助,但是你只限于一小部分可能的约束:接口,基类,值与引用类型和默认构造函数约束,就是这样。

  • PRO:对于两种不同的结构,语义分析可能完全不同。如果你想要,那就太棒了。

  • CON:对于两种不同的结构,语义分析可能完全不同。如果你不想那样,这就是等待发生的错误。

在C#中,无论构造类型的次数是多少次,语义分析都是完成,因此需要使用符合约束条件的任何类型参数,而不仅仅是实际提供的类型参数。

  • PRO:您只生成完全符合您需要的构造的代码。

  • CON:您为所使用的所有构造生成代码。

模板可能会导致codegen变大。在C#中,一般类型的IL生成一次,然后在运行时抖动为程序使用的所有类型执行codegen。这具有较小的性能成本,但由于抖动实际上仅为所有引用类型参数生成一次代码,因此可以稍微减轻这种影响。因此,如果您有List<object>List<string>,则jitted代码仅生成一次并用于两者。相比之下,List<int>List<short>会两次使用代码。

  • PRO:当您使用模板库时,您就拥有源代码。

  • CON:要使用模板库,您必须拥有源代码。

在C#中,泛型类型是一流类型。如果将它们粘贴在库中,则可以在任何地方使用该库,而无需发送源代码。

最后:

  • PRO:模板允许模板元编程。

  • CON:新手难以理解模板元编程。

  • CON:模板系统实际上不允许某些类型拓扑在通用系统中非常简单。

例如,我想在C ++中很难做到这样的事情:

class D<T> 
{
    class S { }
    D<D<T>.S> ds;
}

在C#泛型中,没问题。在运行时,类型仅为所有引用类型参数构建一次

但是在C ++模板中,当你D<int>时会发生什么?内部类型构造了D<D<int>.S>类型的字段,因此我们需要构造该类型。但是那种类型会构造一个D<D<D<int>.S>.S>类型的字段......依此类推到无穷大。

答案 1 :(得分:3)

Java / C#泛型有哪些限制?

Java泛型是有限的,因为不可能像C ++那样做一些技巧。

证明声明here是一个C ++示例,在Java中单独使用模板无法重现。

基于策略的编程是一种在编译时将(模板化)类的使用限制为继承的其他(可能的)模板类的方法。

编译时泛型与运行时泛型有什么关系?

协议是编译器知道关于类/模板的可能运行时行为的所有内容,因此它可以使用C#/ Java /任何运行时环境/编译器进行(目前)不可能的大量优化。

另一个好处是编译器可以确保模板组合的实例化是有效的,这意味着当程序员想要实例化时,不可能像Java / C#中那样发生运行时错误。具有无效组合的新对象。

C ++泛型的缺点是什么?

缺点是模板读取,理解和调试变得非常复杂。这可能是Java开发人员不希望在某种语言中拥有这样一种野兽的原因之一。


使用Java / C#泛型不可能使用C ++ Generics有什么可能?

在C ++中可以使用其他模板作为模板参数,这在C#/ Java中是不可能的,并且允许使用模板元编程等优雅技巧。

答案 2 :(得分:3)

Java Generics的动机始终是提供类型安全性,同时保持向后兼容性。 Sun通过添加类型检查然后在编译过程中删除泛型类型来实现泛型。代码如:

// This applies to JDK 1.5, so I won't use <>.
List<Number> list = new ArrayList<Number>();
list.add(2.0);
list.add(-2);
list.add(new BigDecimal("1.23456789");

相当于

List list = new ArrayList();
Double temp = new Double(2.0); // boxing
if (!temp instanceof Number) throw new ClassCastException();
list.add(temp);
// Similar for -2 and the BigDecimal.

不知道列表的类型使其成为运行时类,但编译器可能会删除一些instanceof作为安全。

由于编译器没有将泛型类型写入已编译的类文件中,因此上面的list.getClass() == ArrayList.class中没有像C ++中那样的模板特化。 List<Boolean>无法打包成一系列位。所有泛型类型都是类型,与C ++中的模板不同,如:

template<int length, int time, int mass>
class measurement {...}

可用于尺寸分析,并防止人们增加区域长度。

答案 3 :(得分:1)

根据MSDN,C#泛型和C ++模板之间的主要区别是:

  • C#泛型不提供与C ++模板相同的灵活性。例如,虽然可以调用用户定义的运算符,但是不可能在C#泛型类中调用算术运算符。
  • C#不允许非类型模板参数,例如模板C {}。
  • C#不支持显式专业化;也就是说,特定类型的模板的自定义实现。
  • C#不支持部分特化:类型参数子集的自定义实现。
  • C#不允许将type参数用作泛型类型的基类。
  • C#不允许类型参数具有默认类型。
  • 在C#中,泛型类型参数本身不能是通用的,尽管构造的类型可以用作泛型。 C ++确实允许模板参数。

然而,在某些情况下,您可以使用扩展方法解决其中一些问题。

可能使用C ++泛型,无论是C#泛型还是Java泛型:真正的模板元编程(在编译时图灵完成)。

#include <iostream>
template<unsigned U>
struct Fac{ enum { value = U * Fac<U-1>::value};};
template<>
struct Fac<0>{ enum { value = 1};};
template<unsigned U>
struct Fib{ enum {value = (Fib<U-1>::value + Fib<U-2>::value)};};
template<>
struct Fib<0>{ enum {value = 0};};
template<>
struct Fib<1>{ enum {value = 1};};
template<unsigned U>
void show(){
    show<U-1>();
    std::cout << "Fib(" << U << ")=" << Fib<U>::value << "\t" << "Fac(" << U << ")=" << Fac<U>::value << std::endl;
}
template<>
void show<0>(){}

int main(int argc, char** argv){
    show<12>();
}

http://ideone.com/Gdf3W

修改

C ++标准在C#Java时没有对类型参数的限制。 Boost有类似的东西(Boost Concept Check Library)。但是从C ++ 11开始,你现在可以使用<type_traits>来获得类似的东西。