我从这里阅读了一篇有趣的文章/线程/讨论,我收到了以下问题:
- Java/C# 泛型的限制是什么?
- Java/C# 泛型无法实现的 C++ 模板有哪些可能?
编辑 1 Eric Lippert的更多推荐问题
- 有哪些模式可以用 C# 泛型但不能用 C++ 模板?
- C# 的真正泛型类型和 Java 的类型擦除泛型类型有什么区别?
我从这里阅读了一篇有趣的文章/线程/讨论,我收到了以下问题:
编辑 1 Eric Lippert的更多推荐问题
首先,您可能想阅读我 2009 年关于此主题的文章。
在我看来,C++ 模板和 C# 泛型之间的主要区别在于,C++ 模板实际上在构建模板时完全重新编译了代码。C++ 方法的优点和缺点有很多:
PRO:您可以有效地创建诸如“类型参数 T 必须具有加法运算符”之类的约束;如果代码包含几个相互添加的 T,那么如果您使用不允许添加的类型参数构造模板,则模板将无法编译。
CON:您可能会意外创建未记录的约束,例如“类型参数 T 必须具有加法运算符”。
在 C# 中,您必须说明哪些约束对用户有帮助,但您仅限于一小部分可能的约束:接口、基类、值与引用类型和默认构造函数约束,仅此而已。
PRO:对于两种不同的结构,语义分析可能完全不同。如果你想要,那太棒了。
CON:语义分析对于两种不同的结构可能是完全不同的。如果您不希望这样,那是一个等待发生的错误。
在 C# 中,无论类型构造多少次,语义分析都会执行一次,因此需要使用任何满足约束的类型参数,而不仅仅是实际提供的类型参数。
PRO:您只为您需要的结构生成代码。
CON:您为您使用的所有结构生成代码。
模板会导致 codegen 变大。在 C# 中,泛型类型的 IL 生成一次,然后在运行时抖动对程序使用的所有类型进行代码生成。这具有很小的性能成本,但由于抖动实际上只为所有引用类型参数生成一次代码这一事实而有所缓解。因此,如果您有List<object>
,List<string>
然后jitted 代码只生成一次并用于两者。相比之下,两次 jits 代码List<int>
。List<short>
PRO:当您使用模板库时,源代码就在那里。
缺点:要使用模板库,您必须拥有源代码。
在 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>
... 的字段,以此类推直到无穷大。
Java 泛型是有限的,因为它不可能像在 C++ 中那样做一些技巧。
为了证明这里的声明是一个 C++ 示例,该示例无法在 Java 中单独使用模板来重现。
基于策略的编程是一种在编译时将(模板化)类的使用限制为其他(可能的)模板化类继承的方法。
编译时泛型与运行时泛型有什么大不了的?
交易是编译器了解类/模板可能的运行时行为的所有信息,因此它可以进行大量优化,而 C#/Java/任何运行时环境/编译器(目前)是不可能的。
另一个好的方面是编译器可以确保模板组合的实例化是有效的,这意味着当程序员想要实例化新对象时,不会像在 Java/C# 中那样发生运行时错误。无效的组合。
C++ 泛型的缺点是什么?
缺点是模板的阅读、理解和调试会变得非常复杂。这(也许)是 Java 开发人员不想在语言中拥有这样一个野兽的原因之一。
在 C++ 中可以使用其他模板作为模板参数,这在 C#/Java 中是不可能的,并且允许使用模板元编程之类的优雅技巧。
Java 泛型的动机始终是提供类型安全,同时保持向后兼容性。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.
不知道 list 的类型使其进入运行时类,但instanceof
编译器可能会删除一些 s 以保证安全。
由于编译器不会将泛型类型写入已编译的类文件中,因此上述list.getClass() == ArrayList.class
情况,不能像 C++ 中那样存在模板特化。List<Boolean>
不能打包成一个比特序列。所有泛型类型都是类型,不像 C++ 中的模板,例如:
template<int length, int time, int mass>
class measurement {...}
它可用于尺寸分析,并防止人们增加区域的长度。
根据MSDN,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>();
}
编辑
C++ 标准对类型参数没有限制,而C#和Java有。Boost 有类似的东西(Boost Concept Check Library)。但是从 C++11 开始,您现在可以使用<type_traits>
来获得类似的东西。