目录
10.1 “类模板”还是“模板类”
10.2 替换,实例化,和特例化
10.3 声明和定义
10.3.1 完整类型和非完整类型(complete versus incomplete types)
10.4 唯一定义法则
10.5 Template Arguments versus Template Parameters
10.6 总结
参考:GitHub - Walton1128/CPP-Templates-2nd--: 《C++ Templates 第二版》中文翻译,和原书排版一致,第一部分(1至11章)以及第18,19,20,21、22、23、24、25章已完成,其余内容逐步更新中。 个人爱好,发现错误请指正
关于该如何称呼一个是模板的类,有一些困扰:
术语 class template 是指这个 class 是模板。也就是说它是一组 class 的参数化表达。
术语 template class 则被:
用作 class template 的同义词。
用来指代从 template 实例化出来的 classes。
用来指代名称是一个 template-id(模板名 + )的类。 第二种和第三中意思的区别很小,对后续的讨论也不重要。
用实际参数替换模板参数,以从一个模板创建一个常规类、类型别名、函数、成员函数或者 变量的过程,被称为“模板实例化”。
通过实例化或者不完全实例化产生的实体通常被称为特例化(specialization)。
但是在 C++中,实例化过程并不是产生特例化的唯一方式。另外一些方式允许程序员显式的 指定一个被关联到模板参数的、被进行了特殊替换的声明。正如 2.5 节介绍的那样,这一类 特例化以一个 template<>开始:
template // primary class template
class MyClass { …
};
template<> // explicit specialization
class MyClass { …
};
严格来说,这被称为显式特例化(explicit specialization)。
如果特例化之后依然还有模板参数,就称之为部分特例化。
template // partial specialization
class MyClass { …
};
template // partial specialization
class MyClass { …
};
“声明”是一个 C++概念,它将一个名称引入或者再次引入到一个 C++作用域内。引入的过 程中可能会包含这个名称的一部分类别,但是一个有效的声明并不需要相关名称的太多细 节。
注意,在 C++中虽然宏和 goto 标签也都有名字,但是它们并不是声明。
对于声明,如果其细节已知,或者是需要申请相关变量的存储空间,那么声明就变成了定义。 对于 class 类型的定义和函数定义,意味着需要提供一个包含在{}中的主体,或者是对函数使 用了=defaul/=delete。对于变量,如果进行了初始化或者没有使用 extern,那么声明也会变 成定义。下面是一些“定义”的例子:
class C {}; // definition (and declaration) of class C
void f(int p) { //definition (and declaration) of function f()
std::cout << p << ’\n’;
}
extern int v = 1; // an initializer makes this a definition for v
int w; // global variable declarations not preceded by extern are also definitions
非完整类型是以下情况之一:
一个被声明但是还没有被定义的 class 类型。
一个没有指定边界的数组。
一个存储非完整类型的数组。
Void 类型。
一个底层类型未定义或者枚举值未定义的枚举类型。
任何一个被 const 或者 volatile 修饰的以上某种类型。其它所有类型都是完整类型。
C++语言中对实体的重复定义做了限制。这一限制就是“唯一定义法则(one-definition rule, ODR)”。
完整的关于 ODR 的介绍请参见附录 A。目前只要记住以下基础的 ODR 就够了:
常规(比如非模板)非 inline 函数和成员函数,以及非 inline 的全局变量和静态数据成 员,在整个程序中只能被定义一次。
Class 类型(包含 struct 和 union),模板(包含部分特例化,但不能是全特例化),以 及 inline 函数和变量,在一个编译单元中只能被定义一次,而且不同编译单元间的定义 应该相同。
编译单元是通过预处理源文件产生的一个文件;它包含通过#include 指令包含的内容以及宏 展开之后的内容。
可链接实体(linkable entity)指的是下面的任意一种:一个函数或者成员 函数,一个全局变量或者静态数据成员,以及通过模板产生的类似实体,只要对 linker 可见 就行。
template
class ArrayInClass {
public:
T array[N];
};
ArrayInClass
注意模板名称后面的尖括号以及其中的模板实参 。
不管这些实参是否和模板参数有关,模板名称以及其后面的尖括号和其中的模板实参,被称 为 template-id。
有必要对模板参数(template parameters)和模板实参(template arguments)进行区分。简 单来讲可以说“模板参数是被模板实参初始化的”。或者更准确的说:
模板参数是那些在模板定义或者声明中,出现在 template 关键字后面的尖括号中的名 称。
模板实参是那些用来替换模板参数的内容。不同于模板参数,模板实参可以不只是“名 称”。
当指出模板的 template-id 的时候,用模板实参替换模板参数的过程就是显式的,但是在很 多情况这一替换则是隐式的(比如模板参数被其默认值替换的情况)。
一个基本原则是:任何模板实参都必须是在编译期可知的。就如接下来会澄清的,这一要求 对降低模板运行期间的成本很有帮助。由于模板参数最终都会被编译期的值进行替换,它们 也可以被用于编译期表达式。在 ArrayInClass 模板中指定成员 array 的尺寸时就用到了这一特 性。数组的尺寸必须是一个常量表达式,而模板参数 N 恰好满足这一要求。
对这一特性的使用可以更进一步:由于模板参数是编译期实体,它们也可以被用作模板实参。 就像下面这个例子这样:
template
class Dozen {
public:
ArrayInClass contents;
};
其中 T 既是模板参数也是模板实参。这样这一原理就可以被用来从简单模板构造更复杂的模 板。当然,在原理上,这和我们构造类型和函数并没有什么不同。
类型可以是完整的或者非完整的。
根据唯一定义法则(ODR),非 inline 函数,成员函数,全局变量和静态数据成员在整 个程序中只能被定义一次。