浅谈C++与C#泛型编程的区别与联系
前言
这阵子群友讨论了很多次C++
模板元编程和C#泛型的区别,但是大家懂得,在QQ群讨论这种体量的论题,你一言我一语很难说清楚,尤其是大家对各种名词的理解和定义不一致的情况下,所以我决定今天整一篇文章,结合代码,浅谈一下C++
和C#泛型编程的区别与联系。
个人水平有限,欢迎大家指正,补充。
名词定义
正如上面所说,每个人对于各种名词的认识之间可能存在着差别,所以在这里统一用我的方式定义一下以下名词
- 泛型:泛型是一种程序语言设计技术,具体体现为,我们事先写好一份泛型程序(这里叫模板更合适),其中有一些部分或者全部是运行时根据我们的写法确定的,然后生成这部分的新实例,最后执行的是这些运行时生成的新实例
- C#泛型:C#2.0新增的泛型技术
C++
泛型/模板/模板元:即与template关键字相关的泛型技术
正文
由于C++
的泛型功能可以算是C#泛型的超集,所以下面就以C++
各项泛型功能为基准对比这两个语言的泛型功能
泛型函数
C++
1 | class Generic_A |
C#
1 | public class Generic_A |
泛型类
C++
1 | template<typename T> |
C#
1 | public class Generic_A<T> |
泛型参数
参数类型
C++:模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔型,不同位数、有无符号的整型,甚至包括指针。
C#:只能是类型
可变参数
C++
1 | template<typename ...Args> |
C#
仅支持可变数目的同类型参数,不支持可变类型的参数
1 | public class Generic_A |
默认参数
C++
1 | class Generic_A |
C#
不支持
控制实例化
C++
C++中的泛型被使用时会被实例化,这意味着,相同类的实例可能出现在多个文件中,针对这一问题,可以用**显式实例化(extern)**来避免不必要的开销
C#
CLR内核优化,例如,特定类型实参调用了一个方法,以后再用这个类型实参调用这个方法,CLR只会为这个方法/类型组合编译一次代码,
此外,CLR认为所有引用类型实参都一样,所以可以代码共享,例如,为List<String>
方法编译的代码可以直接用于List<Stream>
方法,这是因为所有引用类型实参/变量只是指向托管堆的一个8字节指针(这里假设64位系统),但是对于值类型,则必须每种类型都进行代码生成,因为值类型大小不定
总结
C++
模板与 C#
泛型实例化方面存在着显著的差别。C# 泛型在编译期会变成 IL,而泛型T会被变成一个占位符,可被替换,这使得在运行时会为每个值类型创建相应的专用类型(也就是JIT选择对应的类型去填充那个T占位符),而为引用类型只会创建一次专用类型;C++
模板实际上是代码扩展宏,它为提供给模板的每个类型参数生成一个专用类型。因此,当 C++
编译器遇到模板(例如整数 Stack)时,它会将模板代码扩展为 Stack 类并将整数作为该类本身的类型包含在其中。无论类型参数是值类型还是引用类型,如果不专门设计链接器来降低代码膨胀速度,C++
编译器每次都会创建一个专用类,从而导致比使用 C#
泛型更显著的代码膨胀速度。
特化/偏特化
C++
1 | // 首先,要写出模板的一般形式(原型) |
C#
不支持
语法检测
C++
参照双阶段名称查找,部分报错无法在编辑时,甚至编译期给出,只有在运行时才报出
C#
强制转型方面,在没有泛型约束的时候,参数被当做object
其余语法错误可正常给出
参考资料
《CLR Via C#》
《C++ Primer》