在C++中模板分为两大类别:类模板和函数模板。这两种类别的模板在语法形式上是相同 的,只是各自存在一些特别的约束。那么什么样的C++元素可以作为实参来替换模板中的形参 呢?这里又主要分成两大类实参类型: 1. 类型实参 类型实参实际上就是C++中间的各种各样的数据类型,包括POD类型和类类型。比如: char、int、int*、int&、float和用户定义的类型。 2. 非类型实参 非类型实际上就是值的意思,而要作为模板实参的值就必须是一个常量值,更准确 的说就必须是一个在编译期能够确定的值(简称编译期常量),然而编译期常量在 C++中包含了非常广泛的概念,也不是所有的编译期常量都可以作为模板的实参的, 也就是仅仅只有编译期常量的一个子集可以作为模板实参,那么什么编译期常量可 以作为模板实参呢?主要有三种类型的编译期常量: a. 整型常量 在C++中所谓的整型包括char、short、int、long、long long、上述类型的 无符号类型、wchar_t、enum类型。其中要注意的是float和double类型是不 可以作模板实参的(在C++0x标准中会改变这一状况)。 b. 函数地址 函数地址主要包括非成员函数地址、成员函数地址、静态成员函数地址。 c. 具有外部引用的字符串数组 什么是具有外部引用的字符串数组呢?就是具有如下形式: extern char cstr[]; // 可以初始化,如:extern char cstr[] = "" // char也可以是wchar_t 需要注意的是extern char *cstr;所声名的是一个指针而不是一个数组,所 以不能够用作模板的实参。那么在作为模板实参的时候是采用了cstr所包含 的字符串值作为模版实参的吗,其实不是,虽然cstr是一个字符串,其实在 作为模板实参是仅仅用到了cstr的地址,也就是说如下的定义是合法的: extern char cstr1[] = "cstr"; extern char cstr2[] = "cstr"; template<char *V> struct CTemplateValue {}; // 或者 template<char V[]> struct CTemplateValue {}; template<> struct CTemplateValue<cstr1> {}; template<> struct CTemplateValue<cstr2> {}; 上面的代码中,cstr1和cstr2都包含有相同的字符串值,但是在作为模板实参 时并没有参考cstr1和cstr2的内容,而是依赖于其地址,所以可以进行模板的 特化,而不出现重复特化的编译时错误。 总的来说这里上面的三种常量都是编译期常量,对于第一种情况是比较好理解的,而 对于第二三种请款可能就不那么直观了,为什么函数地址和外部引用的字符串是编译 期常量呢?首先我们来解释一下编译器在编译函数和全局外部引用的变量时会如何处 理这两者的地址,以使得其他的代码或模块能够顺利地找到该地址并调用函数或引用 变量。 首先可以肯定的是编译器必须为函数和全局外部引用的变量生成唯一的入口地址,否 则就无法引用了; 其次编译器会生成什么样的地址呢?地址分为:物理地址(PA)和虚拟地址(RVA), 目前编译器一般不会生成物理内存地址的,因为基本上所有的程序在操作系统调度运 行时均不可能保证该物理内存地址可以被分配给该程序使用(可能已经被其他的程序 占用了),那么现在的编译器均会选择生成RVA地址,其实RVA地址是相对于程序载入 首地址的一个偏移常量(offset),那么操作系统在载入程序时仅仅需要修改程序的 载入首地址就可以完成程序的载入,而程序内部在调用一个函数或引用变量时会采用 首地址+RVA地址的方法来完成引用,这样一来RVA就成为了一个编译期的整型常量了, 所以函数地址和全局外部引用字符串的地址就成为了编译期的常量,可以作为模板实 参了。那为什么必须是外部引用的字符串数组呢?内部引用的字符串数组不可以么? |
|